Effective C ++ 2e Item42

zhaozj2021-02-11  253

Terms 42: Smartly use private inheritance

Terms 35 illustrate that C regards public inheritance as a relationship of "being a". It is confirmed by this example: If a class hierarchy, the Student class is inherited from the Person class, in order to successfully call a function, the compiler can be converted to Person if necessary. This example is worth watching again, but now, public inheritance is replaced by private inheritance:

Class Person {...};

Class Student: // This time we private persons {...}; // use private inheritance

Void Dance (Const Person & P); // Everyone will dance

Void Study (const student & s); // Only students learn

Person P; //p is a person Student S; // s is a student

Dance (p); / / correct, P is a person

Dance (s); // error! A student is not a person

Obviously, the meaning of private inheritance is not "one", what is its meaning?

"Don't be busy!" You said. "Before you understand the meaning, let's take a look at the behavior. Private inheritance has those behavioral characteristics?" Well. The first rule on private inheritance is as you can see now: Contrary to public inheritance, if the inheritance between the two classes is private, the compiler generally does not convert the derived class object (such as Student) into a base class. Objects (such as Person). This is why the subject S calls Dance will fail in the above code. The second rule is that members from private base classes have become a private member of derived class, even if they are protected or public members in the base class. Behavioral characteristics are these.

This has led us to the meaning of private inheritance: private inheritance means "implementation with ...". If the class D privately inherits in class B, this is because you want to use some of the code already existing in class B, not because the type B object and the object of the object of the type B have a conceptual relationship. Thus, private inheritance is purely a realization technology. In terms of terms introduced with clause 36, private inheritance means that it is only inherited, and the interface will be ignored. If D privately inherits to B, that is, the D object is used in the implementation of the B object, only this is Private inheritance is meaningless in the software "design" process, but it is only useful when the software "implementation".

Private inheritance means "implementation with ..." This fact will bring a little confusion to the programmer, as classes 40 indicate that "layering" also has the same meaning. How to choose between the two? The answer is simple: use the hierarchy as much as possible, and private inheritance must only be used. When is it necessary? This often refers to the preserved members and / or virtual functions ---- But this problem has been discussed in a while.

Terms 41 provides a way to write a STACK template, and this template generates class saves different types of objects. You should be familiar with that terms. The template is one of the most useful components of C , but once you start frequently, you will find that if you instance a template is 100 times, you may instantiate the code of the template for a hundred times. For example, the STACK template, the code constituting the STACK member function and the code constituting the Stack Member function are completely separated. Sometimes this is inevitable, but even if the template function can actually share the code, this code repeat may still exist. This increase in this target code volume has a name: "Code Expansion" caused by template. This is not a good thing. For some classes, universal pointers can be used to avoid it. The class stored by this method is a pointer, not an object, and it is true:

· Create a class that stores the Void * pointer of the object. · Creating another set of classes, the only purpose is to ensure type security. These classes use the common class in the first step to complete the actual work.

The following example uses the non-template STACK class in Terms 41, and the difference is that the general pointer is stored, not an object:

Class genericstack {public: genericstack (); ~ genericstack ();

Void Push (void * object); void * pop ();

BOOL EMPTY () Const;

Private: struct stacknode {void * data; // node data stacknode * next; // Next node

StackNode (vid * newdata, stacknode * nextnode): Data (newData), NextNode {}};

Stacknode * top; // Top

GenericStack (const genericstack & rhs); / / Prevent copy and genericstack & // assignment (see Operator = (Const GenericStack & RHS); // Terms 27)};

Because this class is stored instead of an object, it is possible that an object is pointed to by multiple stacks (ie, it is pressed into a plurality of stacks). Therefore, it is extremely important that the POP and class destrition functions destroy any StackNode objects, they can't delete the Data pointer - Although I have to delete the StackNode object itself. After all, the StackNode object is allocated inside the GenericStack class, so it is still to be released within the class. Therefore, the implementation of the Stack class in Terms 41 almost completely meets the requirements of the genericstack. Only change is just to replace T in void *.

There is nothing to use in genericstack this class, but many people will easily misuse it. For example, for a stack for saving int, a user incorrectly presses a pointer to the CAT object into this stack, but compiling is passed, because the pointer is a pointer to the void * parameter.

In order to re-obtain the type of security you used to, you have to create an interface class (Interface Class) for GenericStack, like this:

Class IntStack {// INT Interface PUBLIC: VOID PUSH (INT * INTPTR) {S.push (INTPTR);} INT * POP () {Return Static_cast (s.POP ());} BOOL Empty ) const {return s.empty ();} private: genericstack s; // implementation};

Class Catstack {// CAT Interface PUBLIC: VOID PUSH (CatPtr) {S.push (CATPTR);} Cat * Pop () {Return Static_cast (s.pop ());} BOOL Empty ) const {return s.empty ();

Private: genericstack s; // implementation};

As seen, intStack and Catstack are just suitable for specific types. Only INT pointers can be pressed or pop-up intstack, only the CAT pointer can be pressed or pop-up. All InTstract and Catstack are implemented through the GenericStack class, which is reflected by the hierarch (see Terms 40), and intStack and CatStack will share the function code that truly acts in GenericStack. In addition, all members of the intstack and catstack are (implicit) inline functions, which means that the overhead brought by using these interface classes is almost zero.

But what if some users don't realize this? If they mistakenly think using genericstack, or if they recklessly think that type security is not important, what should I do? How can I prevent them from using GenericStack directly from Intstack and Catstack (this will make them easy to make types of errors, and this is the design C to especially avoid)?

no way! No way to prevent it. But maybe there is any way.

I mentioned that I will mention that the relationship between the class is "implemented" between the classes, there is a choice to inherit by private. In this case, this technology is more advantageous than the hierarchy, because it allows you to tell others: GenericStack is not safe, it can only be used to implement other classes. The specific approach is to declare the member function of GenericStack as a protection type:

Class genericstack {protected: genersticstack (); ~ genericstack ();

Void Push (void * object); void * pop ();

BOOL EMPTY () Const;

PRIVATE: ... //.

GenericStack S; // Error! Constructor is protected

Class IntStack: Private genericStack {public: void push (int * INTPTR) {genericstack :: push (point);} int * pop () {return static_cast (genericstack :: POP ());} BOOL EMPTY ) :: empty ();}};

Class catStack: private genericstack {public: void push (cat * catptr) {genericstack :: push (copy stat);} cat * pop () {return static_cast (genericstack :: pop ());} BOOL Empty ) const {return genericstack :: Empty ();}}; intStack is; // correct

Catstack CS; // is also correct

Like a layered method, based on the implementation of private inheritance avoids code repetition, because this type of security interface class only contains inline calls to the genericStack function.

The interface on the GenericStack class is a very good skill, but it is very annoying that all the interface classes are needed. Fortunately, you don't have to do this. You can let templates to generate them. Below is a template, which generates a type of secure stack interface through private inheritance:

Template class stack: private genericstack {public: void push (t * Objectptr) {genericstack :: push (objectptr);} t * pop () {return static_cast (genericstack :: POP ()) } BOOL EMPTY () const {return genericstack :: Empty ();}}

This is a stunning code, although you may not realize it. Because this is a template, the compiler will automatically generate all interface classes based on your needs. Because these classes are type security, the user type error can be found during compilation. Because GeneralStack's member function is the protection type, and the interface class uses GenericStack as a private base class, the user will not be able to bypass the interface class. Because each interface class member function is (implicitly) declares that INLINE, use these types of security classes do not bring running overhead; generated code is like the user directly using GenericStack (assuming the compiler satisfied inline Request --- See Terms 33). Because GenericStack uses the Void * pointer, the code for operating the stack requires only one, regardless of how much different types of stacks are used in the program. In short, this design makes code up to the highest efficiency and highest type of security. It's hard to do better than this.

One of the basic understandings of this book is that the various characteristics of C are interacting in an extraordinary manner. This example, I hope that you can agree, it is indeed extraordinary.

From this example, you can find that if you use a hierarchy, you can't achieve this effect. Only inheritance can access the protection member, only the inheritance makes the virtual function can be redefined. (The existence of virtual functions will trigger the use of private inheritance, as described in Terms 43) Because there is virtual function and protection member, sometimes private inheritance is the only effective way to "implement" relationship between the expression class. Therefore, when private inheritance is the most appropriate implementation method you can use, it is boldly uses it. At the same time, in a broad sense, the layering is the technique that should be preferred, so as long as it is possible, it is necessary to use it as much as possible.

转载请注明原文地址:https://www.9cbs.com/read-4439.html

New Post(0)