Flylib.com

Books Software

 
 
 

FAQ 17.05 How can a base class protect derived classes so that changes to the base class will not affect them?

FAQ 17.05 How can a base class protect derived classes so that changes to the base class will not affect them?

The easiest solution is to define a protected: interface in addition to the public: interface.

A class hierarchy is more resilient to changes if it has two distinct interfaces for two distinct sets of users.

  • Its public: interface serves unrelated classes.

  • Its protected: interface serves derived classes.

Both interfaces must be fully specified. For instance, the actual raw data of a class could be private: with a set of protected: inline member functions for accessing this data. These inline member functions define an interface between the derived classes and the raw bits of the base class. Then the private: data of the base class could be changed within reasonable bounds without affecting the derived classes. It would still be necessary to recompile the derived classes after a change to the base class, though the source code of the derived class would not need to be changed unless the protected: interface is modified in a nonbackward compatible manner.

For example, suppose class Base has an int data member. Base can ensure that derived classes do not rely on the specific data structure by making the data structure private: (in this case, a simple int ) and defining inline protected: members for accessing these data. Derived class Derived accesses the value using these protected: inline member functions.

class Base {
public:
  Base() throw();
protected:
  void storeValue(int value) throw();
  int  retrieveValue() const throw();
private:
  int value_;
};

inline Base::Base()                     throw() : value_(37) { }
inline void Base::storeValue(int value) throw() { value_ = value; }
inline int  Base::retrieveValue() const throw() { return value_; }

class Derived : public Base {
public:
  void f(int i) throw();
};

void Derived::f(int i) throw() { storeValue(i); }

int main()
{
  Derived d;
  d.f(42);
}

FAQ 17.06 Can a derived class pointer be converted into a pointer to its public base class?

Such conversions are possible and don't even require a pointer cast.

A publicly derived class is a kind-of its base class. By implication , the upward conversion is perfectly safe, and is quite common. For example, a pointer to a Car is in fact already pointing at a Vehicle , since a Car is a kind-of a Vehicle .

class Vehicle { };
class Car : public Vehicle { };

void f(Vehicle* v) throw();

void g(Car* c) throw()
{
  f(c);  //Perfectly safe; no cast needed
}

FAQ 17.07 How can a class Y be a kind-of another class X as well as getting the bits of X ?

This is easy: use public inheritance.

Here is the C++ syntax for public inheritance.

class X            { /*bits and/or code go here*/ };
class Y : public X { /*more bits and/or code go here*/ };

This does two distinct things. First, it provides the kind-of relationship: Y is a kind-of X , therefore Y supports the same services as X ( Y might add some new member functions as well). Second, it shares bits and code: Y inherits X 's bits (data structures) and code (algorithms).

FAQ 17.08 How can a class Y get the bits of an existing class X without making Y a kind-of X ?

There are three alternatives. The preferred solution is normal composition, also known as has-a. But in some cases private inheritance should be used, and in a few cases, protected inheritance should be used.

Here is the class X that will be used in each of the three following examples.

class X {
public:
  void f() throw();
  void g() throw();
private:
  int a_;
  float b_;
};

Here is the C++ syntax for composition (that is, Y has-a X ). This is the preferred solution.

class Y1 {
public:
  void f() throw();
protected:
  X x_;

<-- 1

};

void Y1::f() throw()
{ x_.f(); }

<-- 2

(1) Composition: Y1 is not a kind-of X

(2) Y1 calls member functions in X ("reuse")

Here is the C++ syntax for private inheritance, which is semantically the same as has-a but with an increased ripple effect (changes to the protected: part of X can break the private derived class Y2 ). This is the second of the three alternatives.

class Y2 : private X {

<-- 1

public:
  using X::f;

<-- 2

};

(1) Private inheritance: Y2 is not a kind-of X

(2) Same semantics as above: Calls to Y2::f() end up in X::f()

Here is the C++ syntax for protected inheritance, which is semantically the same as has-a but with an even greater ripple effect than private inheritance (changes to the protected: part of X can break the protected derived class Y3 and can also break any classes derived from Y3 ). This is the last of the three alternatives.

class Y3 : protected X {

<-- 1

public:
  using X::f;

<-- 2

};

(1) Protected inheritance: Y3 is not a kind-of X

(2) Same semantics as above: Calls to Y3::f() end up in X::f()

In all three cases, a Y object has a X object, and users of Y are unaware of any relationship between Y and X . For example, user code will not break if the relationship between Y and X changes—or even is eliminated. See FAQ 37.01 for more information on private and protected inheritance.