Effective C ++ 2e Item41

zhaozj2021-02-11  227

Terms 41: Distinguish inheritance and Template

Consider the following two design questions:

· As a student who is determined to dedicate computer science, you want to design a class to represent the stack of the object. This will require multiple different classes because the elements in each stack must be similar, that is, it must only be the same type of object. For example, there will be a class to represent the stack of int, the second class to represent the stack of String, the third class to represent the stack of the String, and so on. You may be very interested in designing a minimal class interface (see Terms 18), so you will limit the operation of the stack in: Creating a stack, destroying the stack, pressing the object into the stack, popping the object, and check if the stack is air. In the design, you will not with the standard library (including Stack - see Terms 49), because you are eager to write this code. Reuse (Reuse) is a thing, but when your goal is to explore the working principle of things, it only has three feet.

· As a pet fan, you want to design a class to express a cat. This will also need multiple different classes because each variety of cats will be a bit different. Like all objects, the cat can be created and destroyed, but as all the cats know, the other things do what the cat does not even eat and sleep. However, every kind of cats and sleep have their own love.

The description of these two issues sounds very similar, but it has caused a completely different design. why?

The answer involves the relationship between the "class behavior" and "objects operated by the class". For stacks and cats, it is to handle a variety of different types (the stack contains objects Type T), the cat is a variety T), but you have to ask yourself: Type T affect the behavior of the class? If T does not affect behavior, you can use templates. If t affects behavior, you need a virtual function to use inheritance.

The following code implements the Stack class by defining a linked list, assumes that the stack type is T:

Class Stack {public: stack (); ~ stack ();

Void Push (Const T & Object); T POP ();

BOOL EMPTY () const; // Stack is empty?

PRIVATE: STRUCT StackNode {// Link table Node T data; // This node data stacknode * next; // Link list in the next node

// stacknode constructor, initialize two domains stacknode (const t & newdata): Data (newData), NextNode {}}

StackNode * Top; // Top of the stack

Stack (Const Stack & RHS); / / Prevent Copy and Stack & Operator = (Const Stack & RHS); // Assignment (see Terms 27)};

Thus, the STACK object will construct the data structure as follows:

Stack Object Top -> Data Next -> Data Next -> Data Next ----------------------- -------------------------------------------------- --------- The StackNode object The list itself is composed of a StackNode object, but that is just a detail of the Stack class, STACKNODE is declared as the private type of Stack. Note that StackNode has a constructor to make sure that all its domains are properly initialized. Even if you close your eyes, you can write a list, but don't ignore some new features of C , such as the constructor in the Struct.

Let's take a look at your implementation of the Stack member function. And many prototypes (prototype) is the same, there is no error check here because there is no thing in the prototype world.

Stack :: stack (): TOP (0) {} // Top Initialization to Null

Void Stack :: Push (const t & object) {top = new stacknode (object, top); // new node placed} // Link table head

T stack :: pop () {stacknode * topofstack = top; // Remember the head node TOP-> next;

T data = topofstack-> data; // Remember Node Data Delete Topofstack;

Return Data;}

Stack :: ~ stack () // Remove all objects in the stack {While (TOP) {stacknode * today = top; // Get the head node pointer TOP = TOP-> Next; // Move the next node DELETE TODIE; / / Delete the front of the head node}}}}}}

Bool Stack :: Empty () const {return top == 0;

These codes have no attractiveness. In fact, the only interesting point is that you can write each member function even if you don't know what to T. (There is actually a hypothesis in the above code, that is, suppose you can call the copy constructor; however, as explained in Terms 45, this is an absolutely reasonable hypothesis) No matter what T is, for the construction, destruction, stack , Out of the stack, determine whether the code written by the stack is empty. In addition to "copy constructor that can call T", STACK's behavior does not depend on T. This is the feature of the template: behavior does not depend on the type.

Transforming the Stack class into a template is very simple, even the boss of Dilbert will write:

Template Class Stack {

... // completely and the same

}

But what about the cat? Why is the cat not suitable for templates?

Rereading the above description, pay attention to this: "Every kind of cats and sleep have their own love." This means that different behaviors must be achieved for each different cat. It is impossible to write a function to handle all the cats, which can only make a function interface, all kinds of cats must implement it. Ah! The method of derived a function interface can only be declared a pure virtual function (see Terms 36): Class Cat {public: Virtual ~ cat (); // See Terms 14

Virtual void Eat () = 0; // All cats eat Virtual void Sleep () = 0; // All cats sleep};

Cat's subclass -, for example, SIAMESE and BRITISHORTHAIREDTABBY - Of course, you have to redefine the inherited EAT and SLEEP function interface:

Class Siamese: Public Cat {public: Void Eat (); Void Sleep ();

...

}

Class BritishshorthaiRedTabby: PUBLIC CAT {public: Void Eat (); Void Sleep ();

...

}

Ok, now I know why the template is suitable for the Stack class and is not suitable for the CAT class. I also know why inheriting the CAT class. The only remaining problem is why inherits is not suitable for the Stack class. Want to know why, try to declare a root of a Stack hierarchy - all other stack classes inherit from this unique class:

Class Stack {// a stack of anythingpublic: Virtual Void Push (const ??? Object) = 0; Virtual ??? Pop () = 0;

...

}

The problem is now obvious. What type of pure virtual function Push and POP declaration? Remember, each subclass must re-declare the virtual function inherited, and the parameter type and return type must be identical to the base class. Unfortunately, an int stack can only be pressed and popped up, and a CAT stack can only press and pop up the Cat object. How do the Stack class declare its pure virtual function to enable users to create an int stack and create a CAT stack? Cold and severe truth is to do. That's why inheriting is not suitable for creating a stack.

But maybe you do things like to secretly touch. Maybe you think you can deceive the compiler by using a general purpose (Void *) pointer. But it turns out that in this case, the general pointer will not be busy. Because you can't avoid this: The declaration of derived class virtual functions will never be in contact with its statement in the base class. However, universal pointers can help solve another different problem, which is related to the efficiency of the class generated by the template. See Terms 42 in detail.

After finishing the stacks and cats, the conclusions obtained below will summarize the following:

· When the type of object does not affect the behavior of the function in the class, you should use a template to generate such a class. · When the type of object is influenced, inheritance is used to get such a class.

Truely digest the meaning of the above two points, you can resent between inheritance or templates in the design.

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

New Post(0)