Reading Notes Objective c ++ Item 39 use private inheritance wisely and cautiously, using tiveprivate
1. Introduction to private inheritance
Item 32 indicates that C ++ treats public inheritance as a "is-a" relationship. Consider an inheritance system. a class Student public inherits from the class Person. If a successful call to a function requires an implicit conversion from Student to Person, the "is-a" relationship will appear. For some instances, using private inheritance instead of public inheritance is also valuable:
1 class Person { ... }; 2 class Student: private Person { ... }; // inheritance is now private 3 4 void eat(const Person& p); // anyone can eat 5 6 void study(const Student& s); // only students study 7 8 Person p; // p is a Person 9 10 11 12 Student s; // s is a Student13 14 eat(p); // fine, p is a Person15 16 eat(s); // error! a Student isn’t a Person
It is clear that private inheritance does not mean the "is-a" relationship. What does it mean?
Before we can see the result, let's take a look at the behavior of private inheritance. The first rule of Private inheritance is as follows:AndOn the contrary, if the inheritance relationship between classes is privte inheritance, the compiler will not convert the Class Object (Student) into the base class Object (Person). This is why calling eat for object s fails. The second rule isEven if the member in the base class isIf it is protected or public, the members inherited from private in the base class will become private members in the derived class..
This is the behavior of private inheritance. We also see the conclusion:Private inheritance means "is-implemented-in-terms-". If you want class D private to inherit from Class B, you do this because you want to use some of the properties in Class B that interest you, instead of having any conceptual relationship between type B and type D. Therefore, private inheritance is purely an implementation technology. (That's why everything you inherit from the private base class is changed to private in your class: everything is just the implementation details .) Use the terms introduced in Item34,Private inheritance means that the implementation is inherited, and the interface should be ignored.. If Class D private inherits from Class B, it means that the implementation of class D object depends on Class B object.Private inheritance is meaningful at the software implementation layer and meaningless at the software design layer..
2. How to choose between private inheritance and combination
Private inheritance means that the fact that "is-implemented-in-terms-of" will make you feel a bit uneasy, Because Item 38 points out composition) it also means "is-implemented-in-terms-". How can we choose between them? The answer is simple:Use a combination whenever possible (Composition). It is used only when private inheritance is required.. When must I use it? It is mainly because when protected members or (and) virtual functions are involved, they are also at the boundary. private inheritance cannot be used for spatial reasons. We will worry about it later. After all, it is at the boundary.
2.1 A simple example of using public inheritance
Suppose we are working on an application involving the Widgets class, and we want to better understand how Widgets is used. For example, we not only want to know how frequently the Widget member functions are called, but also want to know how often the function calls change over time. The program has different behavior outlines in different stages of execution. For example, the use of functions by the compiler is much different from the use of functions during optimization and code generation.
We decided to modify the Widget class to track the number of calls to each member function. At runtime, we regularly check this information, and may also check each Widget object value and other data that we think is useful. To achieve this, we will create a timer so that we can know when to collect the statistics.
We are more willing to reuse code than implement new code. We read the toolset and are very happy to find the following classes:
1 class Timer {2 public:3 explicit Timer(int tickFrequency);4 5 virtual void onTick() const; // automatically called for each tick6 7 ... 8 9 };
This is exactly what we are looking. We can configure any tick frequency for this Timer object. When each tick occurs, it calls a virtual function. We can redefine this virtual function to check the current status of the Widget world. Perfect!
To enable widgets to redefine a virtual function in Timer, Widgets must inherit from Timer. However, public inheritance is not suitable. Because the Widget is not a Timer. Widget customers should not call onTick on a Widget object, because onTick is not a Widget interface. In addition, allowing such function calls makes the customer prone to misuse of the Widget interface, which is a clear violation of Item 18's advice: Making the interface Easy to use correctly and not easily misuse. Public inheritance is not a valid choice here.
2.2 use private inheritance
So here we use private inheritance:
1 class Widget: private Timer {2 private:3 4 virtual void onTick() const; // look at Widget usage data, etc.5 6 ... 7 8 }
With the power of private inheritance, Timer's public onTick function becomes private in the Widget. We put it under the private keyword and re-declare it.
2.3 compostion and two advantages
This is a good design, but if private inheritance is not necessary, it has no value. If we decide to use compostion to replace private inheritance. We can declare an embedded class in the Widget. This class of public inherits Timer, and defines onTick in Timer, and then declare an object of this type in the Widget. The following is the implementation of this method:
This design is more complex than private inheritance because it involves both public inheritance and composition, and introduces a new class (WidgetTimer ). I use this example to remind you that it is worthwhile to train yourself to consider multiple methods (Item 35) If you have multiple methods to solve a design problem ). However, I can come up with two reasons to prove that using public inheritance and combination is better than private inheritance.
First, you may want to use widgets as the base classes of other classes, but you may want to prevent the derived classes from redefining onTick. If widgets inherit from Timer, this is impossible, even if the inheritance is private inheritance. (Recall Item 35. Even if the virtual function is private, the derived class may redefine it.) But if WidgetTImer is private in the Widget and inherits from Timer, the derived class of a Widget does not have access to WidgetTimer, so it cannot inherit it or redefine its virtual function. If you use java or C # And find that C ++ does not block the ability of the derived class to redefine virtual functions (Java uses final methods, C # uses sealed ), now you have a way to simulate this behavior in C ++.
Second, you may want to minimize the compilation dependency of widgets. If the Widget inherits from the Timer, the Timer definition must be obtained when the Widget is compiled. Therefore, the file that defines the Widget must be # include Timer. h. From another perspective, if you remove WidgetTimer from the Widget and the Widget contains only one pointer to WidgetTimer, you can simply declare WidgetTimer In the Widget, no # include any header files related to Timer. For large systems, such decoupling is very important. (For details about compiling dependencies, refer to Item 31)
2.4 examples of more rational private inheritance than combination
Earlier, I pointed out that private inheritance is useful when the derived class wants to access the protected part of the base class or wants to redefine the virtual function of the base class, however, the relationship between classes is "is-implemented-in-terms-of" instead of "is-". However, I also pointed out that there is an edge situation involving space optimization that will encourage you to prefer private inheritance rather than composition ).
This edge condition depends on the Edge: it is applied only to classes without data. There is no non-static data member for this type; there is no virtual function (because the existence of the virtual function will add a vptr pointer for each object, see Item 7 ); there is no virtual base class (because such a base class also introduces indirect charges, see Item40 ). In terms of concept, such an empty class object should not use space because there is no data in the object to be saved. However, due to technical reasons, C ++ makes the independent object have to occupy space, so if you write the following code:
1 class Empty {}; // has no data, so objects should 2 // use no memory 3 4 class HoldsAnInt { // should need only space for an int 5 6 7 private: 8 int x; 9 10 Empty e; // should require no memory11 12 };
You will find that sizeof (HoldsAnInt)> sizeof (int): An Empty data member also occupies space. For most compilers, sizeof (Empty) is 1, because the C ++ rule inserts a char into the "empty" object by default when processing an independent object with a size of 0. However, the demand for memory alignment (see Item 50) may cause the compiler to add padding to classes such as HoldsASnInt. Therefore, the HoldsAnInt object will not only have one char larger, in fact, it will increase enough space to accommodate the second int. (In all compilers I tested, the padding described above does .)
However, you may have noticed that the "independent" (freestanding) object must not occupy 0 space. This restriction cannot be applied to the base class of the derived class object, because they are not "independent. If you inherit from the Empty class instead of containing an Empty object,
1 class HoldsAnInt: private Empty {2 private:3 int x;4 };
You will find sizeof (HoldsAnInt) = sizeof (int ). This is called EBO (empty base optimization) and all compilers I have tested have passed this test. If you are a database developer and their customers are very interested in space, it is worth learning about EBO. And you need to knowEBOGenerally, it is feasible only under single inheritance.. Rules for managing the C ++ object layout usually mean that EBO cannot be applied to a derived class with multiple base classes.
In fact, the "empty" class is not really empty. Although they will never have non-static data members, they usually include typedefs, enums, static data members, or non-virtual functions. STL has many technically empty classes that contain useful members (usually typedefs), including the base classes unary_function and binary_function. User-Defined Function objects inherit these classes. Thanks to the extensive use of EBO, these inheritance rarely increases the size of the derived class.
2.5 Conclusion
Let's go back to the basic topics. Because most classes are not empty, EBO is not a valid reason to use private inheritance. Furthermore, most inheritance corresponds to "is-a", which is also a work of public inheritance rather than private inheritance. Both combination and private inheritance mean "is-implemented-in-terms-of", but combination is easier to understand, so you should use it whenever possible.
When you process two classes, they are not "is-a" relationship. a class either needs to access the protected member of another class or need to redefine one or more of its virtual functions, in this case, private inheritance is a valid design policy in most cases. Even in this case, we can see that the hybrid use of public inheritance and inclusion usually produces the behavior we need, although increasing the design complexity.Wise and cautiousUsing private inheritance means that, after you have considered all alternative methods, in your software, it is the best way to represent the relationship between two classes. In this case, you can use it.
3. Summary
- Private inheritance means "is-implemented-in-terms-of". It is usually a lower level than the combination, but it makes sense to use Private inheritance when the derived class needs to access the protected base class member or the inherited virtual function needs to be redefined.
- Unlike a combination, private inheritance can be optimized using a null base class. This is important for database developers who strive to reduce the object size.