Declare the destructor of the polymorphism base class as a virtual function.
Now we want to consider a timer problem. First we create a base class named timekeeper, and then create various derived classes based on it, so that we can use different methods for timing. Because there are many timing methods, it is worthwhile to do so:
Class timekeeper {
Public:
Timekeeper ();
~ Timekeeper ();
...
};
Class atomicclock: Public timekeeper {...}; // Atomic Clock
Class waterclock: Public timekeeper {...}; // water clock
Class wristwatch: Public timekeeper {...}; // watch
Many client programmers don't have to worry about how they calculate the access time, so they can use a factory function to return a pointer to a timer object at this time, this function returns a base class pointer pointing to a newly created derived class object:
Timekeeper * gettimekeeper ();
// Returns an object that inherits the dynamic allocation from timekeeper.
In order not to break the convention of the factory function, the objects returned by gettimekeeper are placed on the stack. Therefore, you must delete each returned object at an appropriate time to avoid leakage of memory or other resources:
Timekeeper * PTK = gettimekeeper (); // obtained from the timekeeper Layer
// A dynamically allocated object
... // Use this object
Delete PTK; // release it to prevent resource leakage
It is not a good practice to push the release work to the client programmer. This is explained in article 13th. For details about how to modify the interface of the factory function to prevent common client errors, see section 18th. However, these topics are not main here. In this article, we mainly discuss a more basic topic, that is, the code above has more fundamental weaknesses: even if the client programmer has done everything perfectly, we still cannot predict what kind of behavior the program will do.
The problem is: gettimekeeper returns a pointer (for example, atomicclock) pointing to an object in its derived class. This object is deleted through a base class pointer (for example, a timekeeper * pointer ), the base class (timekeeper) has a non-virtual destructor. Disaster is buried here because C ++ makes such a rule: When a derived class object is deleted by a pointer to the base class, in addition, this base class has a non-virtual destructor, and the results at this time are unpredictable. In general, the newly derived parts in the derived class cannot be destroyed at runtime. If gettimekeeper returns a pointer to the atomicclock object, the part of the object (atomicclock, that is, the new data member in the atomicclock class) may not be destroyed, the analyzer of atomicclock may not run. However, the basic part of this object (that is, the timekeeper part) is naturally destroyed, and a strange "partially destroyed" object is generated. This method can be used to leak resources, damage data structures, and waste debugging time.
The method to eliminate this problem is simple: provide a virtual destructor for the base class. In this case, if you delete a derived class object, the program runs exactly as needed. The entire object will be destroyed, including all newly derived parts:
Class timekeeper {
Public:
Timekeeper ();
Virtual ~ Timekeeper ();
...
};
Timekeeper * PTK = gettimekeeper ();
...
Delete PTK; // now, the program runs normally
Generally, base classes such as timekeeper contain virtual functions other than the destructor, this is because the objective of a virtual function is to allow the implementation of a derived class to customize them (see article 34th ). For example, for the getcurrenttime function in the timekeeper class, it may have different implementation methods in different Derived classes and must be declared as a virtual function. Almost all classes with virtual functions must contain a virtual destructor.
If a class does not contain a virtual function, it usually means that it will not be used as a base class. When a class is not a base class, it is usually not a good idea to declare its destructor as virtual. See the following example. This class represents vertices in two-dimensional space:
Class Point {// 2D point
Public:
Point (INT xcoord, int ycoord );
~ Point ();
PRIVATE:
Int X, Y;
};
Generally, if an int occupies 32 digits, a point object is suitable for a 64-bit register. In addition, such a point object can be passed to functions written in other languages in the form of a 64-bit value, such as C or FORTRAN. However, if the point destructor is virtual, it is another situation.
The implementation of a virtual function requires that its object contain additional information, which is used to determine which virtual function the object needs to call at runtime. Generally, this information takes the form of a pointer, which is called "vptr" ("virtual function table Pointer "). Vptr points to an array containing function pointers. This array is called "vtbl" ("virtual function table"). Each class containing virtual functions has a vtbl associated with it. When a virtual function is called by an object, the vtbl pointed to by the vptr of the object is used. Find a proper function pointer in vtbl and call the corresponding real function.
The implementation details of virtual functions are not important. The important thing is that if the point class contains a virtual function, this type of object will become larger. In a 32-bit architecture, the point object will increase from 64-bit (two int sizes) to 96-bit (two intors plus one vptr). In a 64-bit architecture, the number of point objects increases from 64-bit to 128-bit. This is because the pointer to the 64-bit architecture has a 64-bit size. As you can see, adding a vptr to the point will increase it by 50-100%! In this way, a 64-bit register cannot accommodate a point object. In addition, point objects in C ++ no longer have the same structure as other languages (such as C), because other languages may not have the concept of vptr. Therefore, unless you explicitly add a vptr equivalent (but this is the implementation details of this language and is not portable), point objects cannot communicate with functions written in other languages.
I have to admit that it is most important to declare all the Destructor as virtual for no reason, just as bad as never declaring them as virtual functions. In fact, many people come up with a solution: declare a virtual destructor only when the class contains at least one virtual function.
Even in classes without virtual functions, you may be entangled by non-virtual constructors. For example, the standard string type does not contain virtual functions, but programmers who are astray may still use it as the base class:
Class specialstring: public STD: String {
// This is not a good idea!
// STD: string has a non-virtual destructor.
...
};
At first glance, this code seems to be okay, but if you do not know why you want to convert a pointer pointing to specialstring to a pointer pointing to string, then you use delete for this string pointer, and your program will immediately fall into an unpredictable state:
Specialstring * PSS = new specialstring ("Impending doom ");
STD: string * pS;
...
PS = PSS; // specialstring * pair STD: string *
...
Delete pS; // The result is unpredictable! In practice, * PS
// Specialstring
// Leakage, because specialstring
// The Destructor is not called.
The analysis above is also true for all classes without virtual destructor, including all STL container types (such as vector, list, set, tr1: unordered_map (see article 54th ), and so on ). If you have inherited a standard container or any other class that contains a non-virtual destructor, do not forget this idea! (Unfortunately, C ++ does not provide a mechanism similar to the final class in Java or the sealed class in C # To prevent inheritance)
In some cases, it is very convenient to provide a pure virtual destructor for a class. You can recall that a pure virtual function will change the class to an abstract class-this type cannot be instantiated (that is, you cannot create this type of object ). However, at some time point, you want a class to become an abstract class, but do you have any pure virtual functions? What should you do at this time? Because the abstract class should be used as the base class, and the base class should have a virtual destructor, And because pure virtual functions can create an abstract class, the solution is obvious: if you want a class to become an abstract class, declare a pure virtual destructor in it. The following is an example:
Class awov {// awov = "abstract w/o virtuals"
Public:
Virtual ~ Awov () = 0; // declare a pure virtual destructor
};
This class has a pure virtual analytic function, so it is an abstract class and has a virtual destructor, so you don't need to worry about problems with the destructor. However, there is a twist here: you must provide a definition for the pure virtual destructor:
Awov ::~ Awov () {}// definition of pure virtual destructor
The Destructor works in the following way: first, the destructor of the derived class is called, and then the destructor of the previous base class is called in sequence. The compiler automatically calls the destructor of an awov derived class ~ Awov, so you must be ~ Awov provides a function body. Otherwise, the connector will report an error.
The principle of providing a virtual destructor for the base class is only valid for the multi-state base class (this base class allows its interface to manipulate the type of the derived class. We say that Timekeeper is a multi-state base class, because even if we only have timekeeper pointers to them, we can still manipulate atomicclock and waterclock.
Not all base classes must have polymorphism. For example, standard string and STL containers do not use base classes, so they do not have polymorphism. Some classes are designed as base classes, but they are not designed as polymorphism classes. These classes (for example, uncopyable in article 6th and input_iterator_tag in standard classes (see article 47th) cannot manipulate their derived classes through their interfaces. Therefore, they do not need virtual destructor.
Remember
- The virtual destructor should be declared for the polymorphism base class. Once a class contains a virtual function, it should contain a virtual destructor.
- If a class does not have to be a base class or has no polymorphism, it should not be declared as a virtual destructor.