[ Team LiB ] Previous Section Next Section

Gotcha #91: Failure to Understand Protected Access

The legality of access to a class's members is sometimes a matter of perspective. For example, a base class's public members are private when viewed through the perspective of a privately derived class:



class Inst { 


 public:


   int units() const


       { return units_; }


   // . . .


 private:


   int units_;


   // . . .


};





class Sbond : private Inst {


   // . . .


};


// . . .


void doUnits() {


   Sbond *bp = getNextBond();


   Inst *ip = (Inst *)bp; // old-style cast necessary . . .


   bp->units(); // error!


   ip->units(); // legal


}


This particular situation, while amusing, doesn't arise often. More conventionally, we would have employed public inheritance if we'd wanted to expose the base class interface through the derived class. Private inheritance is employed almost exclusively to inherit an implementation. The necessity of using a cast to convert the derived class pointer to a base class pointer is a strong indicator of a bad design practice.

As an aside, note the required use of an old-style cast for the conversion of the derived class pointer to private base class pointer. We would generally prefer to use a safer static_cast, but we can't in this case. A static_cast can't cast from a derived class to an inaccessible base class. Unfortunately, using an old-style cast will tend to mask errors that may occur if the relationship between Sbond and Inst should later change (see Gotchas #40 and #41). My own position is that the cast should go entirely and the hierarchy should be redesigned.

So let's give the base class a virtual destructor, make the accessor function protected, and derive some proper, substitutable derived classes:



class Inst { 


 public:


   virtual ~Inst();


   // . . .


 protected:


   int units() const


       { return units_; }


 private:


   int units_;


};


class Bond : public Inst {


 public:


   double notional() const


       { return units() * faceval_; }


   // . . .


 private:


   double faceval_;


};





class Equity : public Inst {


 public:


   double notional() const


       { return units() * shareval_; }


   bool compare( Bond * ) const;


   // . . .


 private:


   double shareval_;


};


The base class member function that returns the number of units for a financial instrument is now protected, indicating that it's intended for use by derived classes. The calculation of the notional amount for both bonds and equities uses this information in the calculation.

However, these days it's a good idea to compare an equity to a bond, so the Equity class has declared a compare function that does just that:



bool Equity::compare( Bond *bp ) const { 


   int bunits = bp->units(); // error!


   return units() < bunits;


}


Many programmers are surprised to find that the first attempt to access the protected units member function results in an access violation. The reason for this is that access is being attempted from a member of the Equity derived class but is being made for a Bond object. For non-static members, protected access requires not only that the function making the access be a member or friend of the derived class, but also that the object being accessed have the same type as the class of which the function is a member (or, equivalently, is an object of a publicly derived class) or which is granting access to a non-member friend.

In this case, a member or friend of Equity can't be trusted to provide the proper interpretation of the meaning of units for a Bond object. The Inst base class has provided the units function to its derived classes, but it's up to each derived class to interpret it appropriately. In the case of the compare function above, the comparison of the raw number of units of each instrument type is unlikely to have any useful meaning without additional (and private) derived class-specific information on the face value of a bond or the price of a share. This additional access-checking rule for protected members has the beneficial effect of promoting the decoupling of derived classes.

An attempt to circumvent the access protection violation by passing a Bond as an Inst doesn't help:



bool Equity::compare( Inst *ip ) const { 


   int bunits = ip->units(); // error!


   return units() < bunits;


}


Access to inherited protected members is restricted to objects of the derived class (and classes publicly derived from the derived class) making the call. The best placement for a function like compare, if it's necessary at all, is higher up in the hierarchy, where its presence won't promote coupling among derived classes:



bool Inst::unitCompare( const Inst *ip ) const 


   { return units() < ip->units(); }


Failing that, and if you don't mind a little coupling between Equity and Bond (though you should mind), a mutual friend will do the trick:



class Bond : public Inst { 


 public:


   friend bool compare( const Equity *, const Bond * );


   // . . .


};


class Equity : public Inst {


 public:


   friend bool compare( const Equity *, const Bond * );


   // . . .


};


bool compare( const Equity *eq, const Bond *bond )


   { return eq->units() < bond->units(); }


    [ Team LiB ] Previous Section Next Section