Valid C ++ use private inheritance with caution

Source: Internet
Author: User

 

Given a hierarchy (inheritance system), one class student is inherited from a class person public. When it is necessary to call a function successfully, students needs to be implicitly transformed to persons, it demonstrates this to the compiler. It is worthwhile to use private inheritance (private inheritance) instead of public inheritance (Public inheritance) to redo part of this example:

Class person {...};

Class student: private person {...}; // inheritance is now Private

Void eat (const person & P); // anyone can eat

Void Study (const student & S); // only students study

Person P; // P is a person

Student s; // s is a student

Eat (p); // fine, P is a person

Eat (s); // error! A student isn' t a person
 

Obviously, private inheritance (private inheritance) does not mean is-. So what does it mean?

"Hello !" You said, "before we get the meaning of it, Let's first look at its behavior. What is the behavior of private inheritance ?" Well, the first rule that controls private inheritance (private inheritance) can only be seen from the action: Compared with public inheritance (Public inheritance), if the classes (class) the Inter-inheritance relationship (inheritance relationship) is private, and the compiler generally does not transform a derived class Object (derived class Object) (such as student) into
Base Class Object (base class Object) (such as person ). This is why calling eat for object (object) s fails. The second rule is that Members (Members) inherited from a private base class (private base class) will become the private members (Private member) of the derived class (derived class ), even if they are protected or public in base class (base class.

This is not the case. This gives us meaning. Private inheritance (private inheritance) means is-implemented-in-terms-of (according ...... ). If you make class (class) d inherit from class (class) B private, you do this because you are interested in using some of the features available in class (class) B, it is not because of the conceptual relationship between types (type) B and the objects (object) of types (type) d. Similarly, private inheritance (private inheritance) is purely an implementation technology. (That's Why You
The reason why everything inherited by private base class becomes private in your class: it is all the implementation details .) Using the terms in interface inheritance and implementation inheritance, private inheritance (private inheritance) means that only implementation (Implementation) should be inherited; interface (Interface) should be ignored.

If D inherits from B, it means D objects are implemented in terms of B objects (D object is implemented based on B object), no more. Private inheritance (private inheritance) is meaningless During the software design period and is available only during the software implementation period. Private inheritance (private inheritance) means is-implemented-in-terms-of (according ...... The fact is a bit confusing, as mentioned in the article "simulating" has-a "through composition"
Composition (composite) also has the same meaning. How do you choose between them in advance? The answer is simple: as long as you can use composition (composite), you can only use private inheritance (private inheritance) when absolutely necessary ). When is it absolutely necessary? When protected members and/or virtual functions are involved, there is also an extreme space-related situation that skews the balance to private inheritance (private inheritance. We will worry about this extreme situation later.

After all, it is just an extreme situation. Suppose we work on an application that contains widgets, and we think we need to better understand how widgets is used. For example, we need to know not only the frequency of widget member functions (member function) calls, but also how call ratios (call rate) changes over time. Programs with clear execution stages can have different behavior focuses at different execution stages. For example, the use and optimization of functions in the parsing phase of a compiler are very different from those in the code generation phase.

We decided to modify the widget class to continuously track how many times each member function (member function) was called. At runtime, We can periodically check this information, which may be associated with the value of each widget and other data that we find useful. To do this, we need to set up some type of timer (timer) so that we can know when the usage statistics are collected.

Try to reuse existing code as much as possible, instead of writing new code. I rummaged through the box in my toolkit and found the following class with satisfaction ):

Class timer {

Public:

Explicit timer (INT tickfrequency );

Virtual void ontick () const; // automatically called for each tick

...

};
 

This is exactly what we are looking for: a timer object that can set the tick Frequency Based on our needs, and it calls a virtual function (virtual function) every time a tick is made ). We can redefine this virtual function so that it can check the current status of the widget. Perfect!

To redefine a virtual function in timer for widgets, Widgets must inherit from timer. However, public inheritance (Public inheritance) is not suitable in this case. Widget is-a (a) timer is not valid. The customer of a widget should not be able to call ontick on a widget, because it is not a part of the interface (Interface) of a widget. Allowing such a function call will make it easier for the customer to misuse the interface (Interface) of the widget. This is a reference to "making the interface easy to use and difficult to use correctly" in "Making the interface Easy to use correctly, the suggestion that is difficult to use incorrectly obviously violates. Public
Inheritance (Public inheritance) is not the correct option here.

Therefore, we will be inherit privately (secretly inherited ):

Class Widget: Private timer {

PRIVATE:

Virtual void ontick () const; // look at widget usage data, etc.

...

};
 

By using the private inheritance (private inheritance) capability, timer's public (public) ontick function becomes private (private) in the widget, and when we redeclare it, and keep it there. Once again, putting ontick into a public interface (public interface) will mislead the customer into believing that they can call it, which violates the difficulty of making the interface easy to use and difficult to use.

This is a good design, but it is worth noting that private inheritance (private inheritance) is not absolutely necessary. If we decide to use composition (compound) instead, it is also possible. We only need to declare a private nested class (Private nested class) in the widget we inherited from the timer public, where the ontick is redefined, and put an object of that type in the widget ). The following is a summary of this method:

Class widget {

PRIVATE:

Class widgettimer: Public timer {

Public:

Virtual void ontick () const;

...

};

Widgettimer timer;

...

};
 

This design is more complex than the one that only uses private inheritance (private inheritance) because it includes (public) Inheritance) and composition (composition, and the introduction of a new class (widgettimer. To be honest, I show it mainly to remind you that there are more than one path leading to a design problem, and it can also train yourself and consider multiple ways (see C ++ proverbs: minimize compilation dependencies between files). However, I can think of why you are more willing to use public inheritance (Public inheritance) and composition (composite) instead
Private inheritance (private inheritance.

First, you may want to design a widget that allows Derived classes (derived class), but you may also want to disable Derived classes (derived class) from redefining ontick. It is impossible to inherit widgets from timer, even if the Inheritance (inheritance) is private. (Recall C ++ proverbs: consider alternative methods for optional virtual functions, derived classes (derived classes) can redefine virtual functions (virtual functions ), even if they are not allowed to be called .) However, if widgettimer
The widget is private and inherited from timer. The Derived classes (derived class) of the widget cannot access widgettimer, therefore, you cannot inherit from or redefine its virtual functions ). If you have programmed in Java or C # and missed the ability to prohibit Derived classes (derived classes) from redefining virtual functions (that is, the Java final methods (methods) and C # sealed), now you have
The idea of similar behavior in C ++.

Second, you may need to minimize the compilation dependencies (compile dependencies) of widgets ). If the widget inherits from timer, the timer's definition (Definition) must be available when the widget is compiled, so the file defining the widget may have to # include timer. h. On the other hand, if widgettimer is removed from the widget and the widget contains only one pointer (pointer) pointing to a widgettimer, the widget only needs widgettimer.
A simple declaration (Declaration) of Class (class); To use timer, it does not need to # include anything. For large systems, such isolation may be very important (for details about minimizing compilation dependencies (minimizing compilation dependencies), see C ++ proverbs: minimizing compilation dependencies between files).

I mentioned earlier that private inheritance (private inheritance) is mainly used when a class to become a derived class (derived class) needs to be accessed as a base class (base class) or you want to redefine one or more of its virtual functions, but classes (class) is-implemented-in-terms-of, rather than is-. However, I have also said that an extreme situation involving space optimization may make you tend
Private inheritance (private inheritance), rather than composition (composite ).

This extreme situation is indeed very acute: It is only applicable when you process a class without data. Such classes (class) do not have non-static data members (non-static data member); do not have virtual functions (because such a function will exist in every object (object) add a vptr. For more information, see C ++ proverbs: declaring destructor as virtual in a multi-state base class). There is no virtual base classes (virtual base class) (because such base classes may also cause size overhead (size cost )). In theory
Empty classes (empty class) objects (object) should not occupy space, because no per-object (object by object) data needs to be stored. However, due to C ++'s inherent technical reasons, freestanding objects (independent objects) must have a non-zero size (non-zero size), so if you do so.

Class empty {}; // has no data, so objects shocould

// Use no memory

Class holdsanint {// shocould need only space for an int

PRIVATE:

Int X;

Empty E; // shocould require no memory

};
 

You will find that sizeof (holdsanint)> sizeof (INT); An Empty data member (empty data member) needs to be stored. For most compilers, sizeof (empty) is 1, because the C ++ rule is opposed to the zero-size freestanding objects (independent object) generally, it is done by inserting a char in "empty" objects ("null" object. However, alignment requirements (alignment Requirements) may prompt the compiler to add padding to classes (classes) similar to holdsanint, so it is likely
Holdsanint objects not only obtains the size of a char, but may actually expand to a position that is sufficient to occupy the second Int. (On all compilers I have tested, this happens without exception .)

However, you may have noticed that "freestanding" objects ("independent" objects) will not have zero size. This constraint does not apply to base class parts of derived class objects (base class component of the derived class Object) because they are not independent. If you inherit from empty to include an object of this type ).

Class holdsanint: private empty {

PRIVATE:

Int X;

};
 

You will almost always find sizeof (holdsanint) = sizeof (INT ). This item is known for empty base optimization (EBO) and has been implemented by all compilers I have tested. If you are a library developer for a space-sensitive customer, EBO is worth learning. It is also worth noting that EBO is generally only feasible under single inheritance (single inheritance. Rules that govern the C ++ object layout (C ++ object layout) usually mean that EBO is not applicable to derived classes (derived classes) with more than one base ).

In practice, the "empty" classes ("null" class) is not really empty. Although they will never have non-static data members (non-static data members), they often include typedefs, enums (enumeration), static data members (static data members ), or non-virtual functions ). STL has many specialized empty classes (empty classes) that contain useful members (Members) (usually typedefs), including base classes (base classes) unary_function
And binary_function, user-defined function objects (User-Defined Function objects) are generally inherited from these classes (classes. Thanks to the general implementation of EBO, this inheritance rarely increases the size of inheriting classes (inherited classes.

However, we still need to return to the Foundation. Most classes (classes) are not empty, so EBO rarely becomes a reasonable reason for private inheritance (private inheritance. In addition, most inheritance (inheritance) is equivalent to is-a, and this is exactly what public inheritance (Public inheritance) does not private (private. Both composition (composite) and private inheritance (private inheritance) mean is-implemented-in-terms-of (according ...... But composition (composition) is easier to understand, so you should do your best to use it.

Private inheritance (private inheritance) is more likely to become a design strategy in the following cases. When the two classes (classes) You want to handle do not have a is-a (yes) relationship, in addition, one of them also needs to access another protected members or need to redefine one or more of its virtual functions ). Even in this case, we can also see that the hybrid use of public inheritance and containment usually produces the behavior you want, although there is a greater design complexity. Careful use of private inheritance (private inheritance) means that when using it, you have considered all the optional solutions. Only it is your software that explicitly represents two
The optimal method for the relationship between classes (classes.

Things to remember

· Private inheritance (private inheritance) means is-implemented-in-terms of (based on ...... ). It is usually lower-level than composition, but when a derived class (derived class) needs to access protected base class members (protecting base class members) or it is reasonable to redefine inherited virtual functions (inherited virtual functions.

· Unlike composition, private inheritance (private inheritance) can make empty base optimization effective. This may be important for library developers who are committed to minimizing object sizes (object size.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.