Item 7: declare Destructors (destructor) as virtual in polymorphic base classes (polymorphism base class)
By Scott Meyers
Translator: fatalerror99 (itepub's nirvana)
Release: http://blog.csdn.net/fatalerror99/
There are many ways to get the time, so it is necessary to establish a timekeeper base class (base class), and create Derived classes (derived class) for different timing methods ):
Class timekeeper {
Public:
Timekeeper ();
~ Timekeeper ();
...
};
Class atomicclock: Public timekeeper {...};
Class waterclock: Public timekeeper {...};
Class wristwatch: Public timekeeper {...};
Many customers simply want to get time and do not care about how to calculate details.Factory Function(Factory function) -- returns a base class pointer to a newly-created derived class Object (a base class pointer pointing to the object of the new derived class) can be used to return a pointer to a timekeeping object:
Timekeeper * gettimekeeper (); // returns a pointer to a dynamic-
// Ally allocated object of a class
// Derived from timekeeper
Similar to the factory function convention, objects (objects) returned by gettimekeeper are built on heap. To avoid Memory leakage and other resources, it is important that each returned objects (object) is fully deleted.
Timekeeper * PTK = gettimekeeper (); // Get dynamically allocated object
// From timekeeper hierarchy
... // Use it
Delete PTK; // release it to avoid resource leak
Item 13 explains why the dependent customer executes the delete task as error-prone (error tendency), and item 18 explains the interface (Interface) of the factory function) how should we change to prevent common customer errors, but these are secondary here, because in this item, we will focus on a more basic defect in the above Code: even if the customer does everything right, it cannot predict how the program will run.
The problem is that gettimekeeper returns a pointer to a derived class Object (pointer to the object of the derived class) (such as atomicclock), the object (object) through a base class pointer (base class pointer) (that is, a timekeeper * pointer) is deleted, and this base class (base class) (timekeeper) hasNon-virtual destructor(Non-virtual destructor ). This is the cause, because C ++ requires that when a derived class Object (a derived class Object) if a pointer to a base class with a non-virtual destructor is deleted, the result is undefined. A typical consequence of running is that the derived part of the object (derived part of this object) will not be destroyed. If gettimekeeper returns a pointer to the atomicclock object (object), The atomicclock part of the object (that is, the data members (data member) declared in the atomicclock class )) it is likely that it will not be destroyed, and the Destructor (destructor) of atomicclock will not run. However, the base class part (the base class part) (that is, the timekeeper part) may have been destroyed, this leads to a strange "partially destroyed" Object ("partially destroyed" object ). This is a great way to cause resource leakage, damage the data structure, and consume a lot of debugging time.
It is easy to eliminate this problem: Give the base class a virtual destructor (Virtual destructor ). Therefore, when you delete a derived class Object (derived class Object), you will have the expected behavior. The entire object (the entire object) will be destroyed, including all derived class parts (derived class components ):
Class timekeeper {
Public:
Timekeeper ();
Virtual~ Timekeeper ();
...
};
Timekeeper * PTK = gettimekeeper ();
...
Delete PTK; // now behaves correctly
Base classes (base class) similar to timekeeper generally contain other virtual functions except destructor, because virtual functions (virtual functions) the purpose is to allow customization of derived class implementations (implemented by a derived class) (see item 34 ). For example, timekeeper can have a virtual functions (virtual function) getcurrenttime, which has different implementations in different Derived classes (derived classes. Almost all classes with virtual functions should have a virtual destructor ).
If a class does not contain virtual functions, it is often said that it is not intended to be used as a base class (base class. It is usually a bad idea to virtualize A destructor (destructor) when a class (class) is not intended as a base class (base class. Consider a class that represents the points in a two-dimensional space ):
Class Point {// a 2D point
Public:
Point (INT xcoord, int ycoord );
~ Point ();
PRIVATE:
Int X, Y;
};
If an int occupies 32 bits, a point object is applicable to 64-bit registers. In addition, such a point object can be passed as a 64-bit volume to functions written in other languages, such as C or FORTRAN. If the Destructor (destructor) of a point is virtualized, the situation is completely different.
The implementation of virtual functions requires that the object carries additional information, which is used to determine the object (object) at runtime) which virtual functions should be called ). In typical cases, this information has a pointer form called vptr ("virtual table Pointer") (virtual function table pointer. Vptr points to an array of function pointers (function pointer array) called vtbl ("virtual table"), each with virtual functions (virtual functions) each class has an associated vtbl. When a virtual function is called on an object, the actually called function is determined by the following steps: Find the vtbl pointed to by the vptr of the object, then find the appropriate function pointer (function pointer) in vtbl ).
The details of how virtual functions are implemented are not important. It is important that if Point class contains a virtual function, the size of this type of object will increase. In a 32-bit architecture, they will grow from 64 bits (equivalent to two ints) to 96 bits (two ints plus vptr); in a 64-bit architecture, they may grow from 64 bits to 128 bits, because in such an architecture, the pointer size is 64 bits. Adding a vptr to a point will increase its size by 50-100%! Point object is no longer suitable for 64-bit registers. In addition, Point Object (object) does not seem to have the same structure in C ++ and other languages (such as C), because they do not have vptr in other languages. As a result, points is no longer likely to be passed into or out of functions written in other languages unless you make clear compensation for the vptr, and this is its own implementation details and thus no portability.
The final result is to declare all Destructors (destructor) as virtual for no reason, which is the same as never declaring them as virtual. In fact, many people have summarized this rule: declare a virtual destructor in a class if and only if that class contains at least one virtual function (when and only when a class contains at least one virtual function, declare a virtual destructor in the class ).
Even if there is no virtual function, it may be entangled in the non-virtual destructor (non-virtual destructor) problem. For example, a standard string type does not contain virtual functions (virtual functions), but misleading Programmers sometimes use it as a base class (base class:
Class specialstring:Public STD: String{// Bad idea! STD: string has
... // Non-virtual destructor
};
At a glance, this may be harmless, but if you place a pointer-to-specialstring (pointer to specialstring) somewhere in the program for some reason) transformed into a pointer-to-string (pointer to string), then you apply the delete operation to this string pointer (pointer), and you will immediately be evicted to undefined behavior (undefined behavior) territory:
Specialstring * PSS = new specialstring ("Impending doom ");
STD: string * pS;
...
PS = PSS; // specialstring * → STD: string *
...
Delete pS; // undefined! In practice,
// * PS's specialstring Resources
// Will be leaked, because
// Specialstring destructor won't
// Be called.
The same analysis can be applied to any class (class) that lacks virtual destructor (Virtual destructor), including all STL iner (container) types (such as vector, list, set, tr1: unordered_map (see item 54 ). If you are tempted to inherit from standard iner (standard container) or any other class with non-virtual destructor (non-virtual destructor), be sure to hold on! (Unfortunately, C ++ does not provide derivation-prevention mechanism (Anti-derivation mechanism) similar to Java's final classes (class) or sealed classes (class) of C ).)
Sometimes, providing a pure virtual destructor (pure virtual destructor) for a class can provide some convenience. Recall that pure virtual functions (pure virtual function) CausesAbstractClasses (abstract class) -- class that cannot be instantiated (that is, you cannot create this type of objects (object )). However, sometimes you have a class, and you want it to be abstract, but there is no pure virtual functions (pure virtual function ). What should we do? Because an abstract classes (abstract class) is destined to be used as a base class (base class), and because a base class (base class) should have a virtual destructor (Virtual destructor ), because a pure virtual functions (pure virtual function) generates an abstract classes (abstract class), the solution is simple: when you want to become an abstract class (class) declare a pure virtual destructor (pure virtual destructor ). This is an example:
Class awov {// awov = "abstract w/o virtuals"
Public:
Virtual ~ Awov () = 0; // declare pure virtual destructor
};
This class has a pure virtual functions (pure virtual function), so it is abstract, and because it has a virtual destructor (Virtual destructor ), so you don't have to worry about destructor issues. This is a spiral. However, you must provideDefinition(Definition ):
Awov ::~ Awov () {}// definition of pure virtual dtor
Destructors (destructor) works in the following way: the Destructor (destructor) of the Most derived class (the lowest-level derived class) is called first, and then each base class (base class) is called) destructors (destructor ). The compiler generates a Destructors (destructor) pair from its derived classes (derived class ~ Awov call, so you have to make sure to provide a function body for the function. If you do not, the Connection Program will protest.
The rules that provide virtual destructor (Virtual destructor) for base classes only applyPolymorphicBase classes (polymorphism base class) -- base classes (base class) is designed to allow operations on derived class types (derived class type) through base class interfaces (base class interface. Timekeeper is a polymorphic base classes (polymorphism base class), because even if we only point the pointers (pointers) of the timekeeper type to them, we also hope to be able to operate atomicclock and waterclock objects (object ).
Not all base classes are designed for polymorphically (polymorphism ). For example, whether it is standard string type or STL container types (STL container type), it is designed as base classes (base class ), none of them are polymorphic (polymorphism. Although some classes (classes) are designed for base classes (base classes), they are not designed for polymorphically (polymorphism ). Such classes (class) -- for example, uncopyable in item 6 and input_iterator_tag in the standard library (see item 47) -- is not designed to allow base class interfaces (base class Interface) perform operations on derived class objects (derived class object. Therefore, they do not need virtual destructor (Virtual destructor ).
Things to remember
- Polymorphic base classes should declare virtual destructor (Virtual destructor ). If a class has any virtual functions, it should have a virtual destructor ).
- Virtual destructor (Virtual destructor) should not be declared as either base classes (base class) or classes (classes) designed for polymorphically (polymorphism ).