Discussion
Team LiB
Previous Section Next Section

Discussion

Although derived classes usually add more state (i.e., data members), they model subsets, not supersets, of their base classes. In correct inheritance, a derived class models a special case of a more general base concept (see Item 37).

This has direct consequences for correct overriding: Respecting the inclusion relationship implies substitutabilityoperations that apply to entire sets should apply to any of their subsets as well. After the base class guarantees the preconditions and postconditions of an operation, any derived class must respect those guarantees. An override can ask for less and provide more, but it must never require more or promise less because that would break the contract that was promised to calling code.

Defining a derived override that can fail (e.g., throws an exception; see Item 70) is correct only if the base class did not advertise that the operation always succeeds. For example, say Employee offers a virtual member function GetBuilding intended to return an encoding of the building where the Employee works. What if we want to write a RemoteContractor derived class that overrides GetBuilding to sometimes throw an exception or return a null building encoding? That is valid only if Employee's documentation specifies that GetBuilding might fail and RemoteContractor reports the failure in an Employee-documented way.

When overriding, never change default arguments. They are not part of the function's signature, and client code will unwittingly pass different arguments to the function, depending on what node of the hierarchy they have access to. Consider:



class Base {


  // …


  virtual void Foo( int x = 0 );


};





class Derived : public Base {


  // …


  virtual void Foo( int x = 1 );        // poor form, and surprise-inducing


};


Derived *pD = new Derived;


pD->Foo();                              // invokes pD->Foo(1)





Base *pB = pD;


pB->Foo();                              // invokes pB->Foo(0)



It can be surprising to callers that the same object's member function silently takes different arguments depending on the static type they happen to access it through.

Prefer to add the redundant virtual when overriding a function. It makes the intent clearer to the reader.

Beware of inadvertently hiding overloads in the base class. For example:



class Base{// …


 virtual void Foo( int );


 virtual void Foo( int, int );


 void Foo( int, int, int );


};





class Derived : public Base {// …


 virtual void Foo( int );     // overrides Base::Foo(int), but hides the others


};





Derived d;


d.Foo( 1 );                   // ok


d.Foo( 1, 2 );                // error (oops?)


d.Foo( 1, 2, 3 );             // error (oops?)



If the base class's overloads should be visible, write a using declaration to redeclare them in the derived class:



class Derived : public Base {// …


 virtual void Foo( int );      // overrides Base::Foo(int)


 using Base::Foo;              // bring the other Base::Foo overloads into scope


};



    Team LiB
    Previous Section Next Section