- Clause 38 has-a by a composite mold tree or by a material
- Clause 39 judicious and prudent use of private inheritance
Article 38: Through the composite mold tree
has-aor "to achieve it according to something."
A composite (composition) is a relationship between types, and one type of object that contains other types of objects is this relationship:
class Address{ …… };class PhoneNumber{ …… };class Person{public: ……private: std::string name; Address address; PhoneNumber mobilePhone;};
The Person object contains the String,address,phonenumber object, which is the compound. There are several synonyms: layering (layered), containment (included), aggregation (aggregation), embedding (inline).
As mentioned in clause 32, public is a is-a relationship, and the compound is has-a(with one) or is-implemented-in-terms-of (implemented according to something). In the program, it can be divided into two areas (domains). The object in the program is equivalent to something in the real world that you shape, such as an address, a phone number, and such objects belong to the application domain (application domain). Others are artificial replicas of the implementation details, such as buffers (buffers), mutexes (mutexes), lookup trees, and so on, which are implementation domains (implementation domain). When the composition occurs between the application domain objects, the has-a relationship is displayed, and the is-implemented-in-terms-of relationship occurs in the implementation domain.
Distinguishing between is-a and is-implemented-in-terms-of is troublesome. With an example, let's say you need a template to construct a set of classes to represent sets of objects that do not duplicate. First we think of the set template provided with the standard library.
The set of the standard library is implemented by the balance search tree, with an additional overhead of three pointers per element. This allows the operation time complexity of find, insert, remove, and so on to be O (logn) (logarithmic time, logarithmic-time). This is reasonable if the speed is more important than space, but if space is more important than speed, then the set provided by the standard library will not meet our needs.
The set implementation method is many, can be implemented at the bottom using linked lists, the standard library has a list template, so we re-use it.
template<typename T>class Set: public std::list<T>{ ……};
It looks good, but it's wrong. clause 32 once said that public inheritance is a is-a relationship, that is, set is a list and not. For example, set cannot contain duplicate elements, but list can.
Because these two classes are not is-a relationships, public inheritance does not apply. A set object can be implemented according to a list object:
template<calss T>class Set{public: bool member(const T& item) const; void insert(const T& item); void remove(const T& item); std::size_t size() const;private: std::list<T> rep;};
As long as you are familiar with list, you can quickly implement the above several interface functions.
clause 18 argues that the interface is easy to apply correctly and is not easily misused. There is no protocol for the set to follow the STL container, and if you follow it, add a lot of things to the set, which blurs the relationship between set and list. This only clarifies the relationship between set and list.
Summarize
- The meaning of the compound (composition) is completely different from the public inheritance.
- In the application domain (application domain), the compound means has-a; in the implementation domain (Implementation domains), the compound means is-implemented-in-terms-of (implemented according to something).
Clause 39: Judicious and prudent use of private inheritance
Public inheritance is a is-a relationship, * * clause **32 once said and gave an example, what if the example is inherited with private?
class Person{……};class Student: private Person{……};void eat(const Person& p);void study(const Student& s);Person p;Student s;eat(p);eat(s);
eat(s)
will go wrong because private inheritance is not a is-a relationship. If the inheritance relationship is private, the compiler does not automatically convert a derived class object to a base class object, and all the members that inherit base are private in the derived class.
Private inheritance means implemented-in-terms-of (implemented according to something). If Class D inherits class B as private, we are intent on using some of the features already in Class B. Private inheritance is purely an implementation technique (which is why the base class members are private in the derived class: because they are all just implementation of the minutiae). Private inheritance means that only the implementation is partially inherited, and the interface part should be omitted. D inherits B in private form, meaning that the D object is implemented according to the B object.
Private inheritance means is-implemented-terms-of (implemented according to something), and the compound meaning of the * * clause **38 is the same. So how do you choose between the two? The answer is to compound as much as possible and use private inheritance if necessary. For example, when a protected member or virtual function is involved. There is also a radical situation, which is discussed later when there is enough space to kick over the private succession pillar.
Now that we have a widget class, we want to record the number of each member function call and periodically review this information during the run. In order to complete this work, a timer is required:
class Timer{public: explicit Timer(int tickFrequency); virtual void OnTick() const;//定时器滴答一次,此函数调用一次 ……};
The above is a set of adjustable frequency, each tick calls a virtual function, we can redefine that virtual function, to remove the widget state. In order to redefine the virtual function within a timer, the widget must inherit the timer. Because the widget is not a timer, public inheritance is not applicable. There is also a point of view that does not apply to the Public,widget object call Ontick a little strange, would violate the terms: Make the interface easy to use, not easy to misuse.
class Widget: private Timer{private: virtual void onTick() const;//查看Widget的数据等操作 ……};
This design can also be implemented by compounding
class Widget{private: class WidgetTimer: public Timer{ public: virtual void onTick() const; …… }; WidgetTimer timer; ……};
This design is slightly more complex, involving public inheritance and compositing, and the introduction of a new class. We have reason to choose this composite version instead of the private version.
First, the widget may have derived classes, but we might want to prevent Ontick from being redefined in the derived class. If you use private inheritance, the idea above cannot be implemented because derived classes can redefine the virtual function (* * clause **35). If the reuse scheme is used, the widget's derived classes will not be able to take widgettimer, and it will naturally be unable to inherit or redefine its virtual function. This is like sealed in final or C # in Java.
Second, the use of composite solutions can also reduce compilation dependencies. If the widget inherits the timer, when the widget compiles, the timer definition must be courseware, so the widget's definition file must contain the timer's definition file. The composite scheme can move the Widgettimer out of the widget with just one pointer.
Private inheritance is primarily used when a derived class wants to access a protected ingredient that is intended to be a base class, or to redefine one or more virtual functions. At this time, the relationship between the two classes is is-implemented-in-terms-of, not is-a. There is a radical situation involving space optimization that will encourage you to choose private inheritance instead of inheriting and compounding.
This is a very aggressive situation, only applicable to the class you are dealing with without any data. It does not contain the non-static variable, the virtual function, and does not inherit the virtual base class. Such an empty classes object does not use any space, because it does not have any data objects to store. But for technical reasons, C + + objects must have a non-0 size
class Empty{};class HoldsAnInt{private: int x; Empty e;};
sizeof (Holdsanint) >sizeof (int). In most compilers, sizeof (Empty) is 1, usually the C + + official website ordered a char into the object, but the class size has a byte to its requirements.
The size of the independent (non-subordinate) object must not be zero, and this constraint does not apply to the base component within the derived class object, because they are not independent, if they inherit empty, not the compound
class HoldsAnInt: private Empty{private: int x;};
At this point, sizeof (Holdsanint) ==sizeof (int) can be almost determined. This is called the Ebo (empty base optimization; If the customer is very concerned about space, then use Ebo. Ebo generally only under a single inheritance, the rules governing the layout of C + + objects usually indicate that Ebo cannot be implemented derived classes with more than one base.
The empty class is not really empty. They often contain typedef, enum, static, or-virtual functions. SLT has many technical uses of the empty classes, which contains members (usually typedefs), including base classes Unary_function and Binary_function, which are "user-defined function objects", Classes that are usually inherited.
As mentioned earlier, as long as you can choose to compound, but this is not all. Private inheritance can become an orthodox design strategy when confronted with two classes that do not have a is-a relationship, one of which needs to access another protected member, or need to redefine one or more of its virtual functions. After considering other options, it is still considered that private inheritance is the best way to "show the relationship between the two classes" and use it.
Summarize
- Private inheritance means is-implemented-in-terms-of (implemented according to something). It is usually lower than the compound (composition) level. However, it is reasonable to use private when derived class needs to access the members of the protected base class, or to redefine the inherited virtual functions.
- Unlike composite (composition), private inheritance can result in the optimization of empty base. This may be important for library developers who are committed to minimizing the footprint of objects.
"Effective C + +": Clause 38-clause 39