You should not call virtual functions during construction or analysis, because such calls do not work as you think, and what they do will make you very depressed. If you switch to a Java or C # programmer, please pay close attention to this article, because in the C ++ sharp turns, those languages also turn into a bend urgently.
Suppose you have a set of class hierarchies that simulate stock processing, such as the purchase and sale processes. It is very important for such a process to be verified. Therefore, a Transaction object will be created at any time and this creation record is an appropriate requirement in the verification log. Below is a seemingly reasonable solution:
Class Transaction {// base class for all
Public: // transactions
Transaction ();
Virtual void logTransaction () const = 0; // make type-dependent
// Log entry
...
};
Transaction: Transaction () // implementation
{
// Base class ctor
...
LogTransaction (); // as final action, log this
} // Transaction
Class BuyTransaction: public Transaction {
// Derived class
Public:
Virtual void logTransaction () const; // how to log trans-
// Actions of this type
...
};
Class SellTransaction: public Transaction {
// Derived class
Public:
Virtual void logTransaction () const; // how to log trans-
// Actions of this type
...
};
Consider what will happen when executing this line of code:
BuyTransaction B;
Obviously, the constructor of BuyTransaction is called, but first, the constructor of Transaction must be called first, and the base class part of the object in the derived class is constructed before the derived class. The last line of the Transaction constructor calls the logTransaction function, but the result will surprise you. The called logTransaction version is the one in the Transaction, instead of BuyTransaction -- even if the object type to be created is BuyTransaction. During the construction of the base class, the virtual function never matches (go down) down to the derived class. Instead, the behavior of the object is like its type is a base class. In informal terms, virtual functions are not allowed during the construction of the base class. This seemingly incredible behavior has a good reason. Because the base class constructor is executed before the derived class constructor, when the base class constructor is running, the data member of the derived class is not initialized yet. If the virtual function called during the construction of the base class matches (go down) down to the derived class, the function of the derived class will naturally involve local data members, but those data members have not yet been initialized. This opens a pass for undefined behaviors and late debugging nightmares. It is naturally dangerous to call a part of an object that has not been initialized, So C ++ cannot tell you this.
In fact, there are more and more in-depth principles. During the construction of the base class of the derived class object, the object type is of that base class. Not only does the virtual function resolve to the base class, but also uses runtime type information (such as dynamic_cast and typeid) accessories in the language ), the object will also be treated as the base class type. In our example, when the Transaction constructor runs to initialize the base class of the BuyTransaction object, the object type is Transaction. Every accessory of C ++ looks at it from the following perspective and makes it feel that the unique part of the object's BuyTransaction has not been initialized yet, therefore, the safe way to treat them is to turn a blind eye. Before the constructor of a derived class runs, an object is not a derived class object.
The same reason applies to the structure process. Once the destructor of the derived class runs, the data members of the derived class of this object are regarded as undefined values, so C ++ regards them as nonexistent. When you enter the base class destructor, the object becomes a base class object. All the accessories of C ++, such as virtual functions and dynamic_casts, view it like this.
In the above sample code, the Transaction constructor directly calls the virtual function, and the violation to the rule of this Item is obvious. This violation is so explicit that some compilers give warnings. (Others do not) Even with such warnings, this issue is almost certainly exposed before running, because the logTransaction function is a pure virtual function in the Transaction. Unless it is defined (seemingly impossible, but indeed possible), the program will not be connected: The Connection Program cannot find the required implementation of Transaction: logTransaction.
The problem of calling virtual functions in constructors and destructor is not always so easy to detect. If Transaction has multiple constructor functions, each of them must complete the same work. To avoid code duplication, a good software project will use the shared initialization code, including the call to logTransaction, put a private non-virtual initialization function called init:
Class Transaction {
Public:
Transaction ()
{Init () ;}// call to non-virtual...
Virtual void logTransaction () const = 0;
...
Private:
Void init ()
{
...
LogTransaction (); //... that calla virtual!
}
};
This code is similar in concept to the previous version, but it is more sinister, because it is representative and avoids complaints from compilers and connection programs. In this case, because logTransaction is a pure virtual function in Transaction, most runtime systems are called when pure virtual functions are called, the program will abort unexpectedly (a typical result is to provide a message ). However, if logTransaction is a "regular" virtual function (that is, a non-pure virtual function) and is implemented in Transaction, the version will be called and the program will continue to run slowly, you can't imagine why the wrong version of logTransaction is called when a derived class object is created. The only way to avoid this problem is to ensure that virtual functions are never called on the objects you create or destroy in your constructor and destructor, the constructor and destructor call functions must be subject to the same constraints.
However, how can we ensure that the correct version of logTransaction can be called when an object in the Transaction hierarchy is created at any time? Obviously, it is wrong to call virtual functions on this object in the Transaction constructor.
There are different ways to solve this problem. One of them is to convert logTransaction in Transaction into a non-virtual function, which requires the constructor of the derived class to pass the necessary log information to the constructor of Transaction. That function can safely call non-virtual logTransaction. As follows:
Class Transaction {
Public:
Explicit Transaction (const std: string & logInfo );
Void logTransaction (const std: string & logInfo) const; // now a non-
// Virtual func
...
};
Transaction: Transaction (const std: string & logInfo)
{
...
LogTransaction (logInfo); // now a non-
} // Virtual call
Class BuyTransaction: public Transaction {
Public:
BuyTransaction (parameters)
: Transaction (createLogString (parameters) // pass log info
{...} // To base class
... // Constructor
Private:
Static std: string createLogString (parameters );
};
In other words, because you cannot use virtual functions in the construction process of the base class, you should transfer necessary constructor information from the derived class to the base class constructor as compensation. In this example, pay attention to the use of the (private) static function createLogString in BuyTransaction. Using an auxiliary function to create a constructor that passes a value to the base class is generally more convenient (and more readable) than what is needed to pass the value to the base class by initializing the list of members ). Making that function static will not cause the danger of accidentally involving uninitialized data members of a newly generated BuyTransaction object. This is important because the data members are in an undefined state, which is why the virtual function cannot match the derived class first during the base class construction and destructor.
Things to Remember
· Do not call virtual functions during constructor and destructor, because such calls do not match the deeper hierarchy of classes to which the currently executed constructor or destructor belongs.