Calling virtual functions in constructors/destructors

Source: Internet
Author: User

Let's look at a section of code that calls a virtual function directly in the constructor:

1 #include <iostream>
 2 
 3 class base
 4 {
 5 public:
 6     Base () {Foo ();}   < print 1
 7 
 8     virtual void Foo ()
 9     {         std::cout << 1 << std::endl;
One     }
; 
class Derive:public Base {public
:     Derive (): Base (), M_pdata (new int (2)) {}
  18     ~derive () {delete m_pdata;}     virtual void Foo ()         std::cout << *m_pdata << Std::endl;
Private:     int* m_pdata;
};
The 
int main ()
{     base* p = new Derive ();
to     delete p;     0;
33}

The results here will be printed: 1.

This indicates that line 6th executes Base::foo () instead of Derive::foo (), which means that a virtual function "does not work" in the constructor. Why.

When instantiating a derived class object, the base class part is constructed first, and then the derived class part is constructed. That is, when the derive object is created, the base constructor is called first, and the derive constructor is called.

When the base class part is constructed, the derived class is not yet fully created, and in some sense it is just a base class object. That is, when Base::base () executes the derive object is not fully created, it is treated as a base object instead of a derive object, so Foo is bound to base foo.

C + + is designed to reduce error and bug occurrences. Suppose the virtual function is still "in effect" in the constructor, that is, Foo () in Base::base (), and the call is Derive::foo (). When the Base::base () is invoked, the data in the derived class is m_pdata and the execution of Derive::foo () will cause the program to dereference an uninitialized address, resulting in unpredictable or even program crashes (access to illegal memory).

In summary: The base class part is constructed before the derived class part, and the data member in the derived class is not initialized when the base class constructor executes. If a virtual function call in a base class constructor is resolved to call a virtual function of the derived class, and the virtual function of the derived class accesses the uninitialized derived class data, it causes the program to have some undefined behavior and bugs.

For this, the General compiler will give some support. If you declare foo in a base class as a pure virtual function (see the following code), the compiler might: give the symbol an unresolved error (unresolved external symbol) at compile time with a warning or link. If you can generate an executable file, there must be an error at runtime. Because Foo in base::base () always calls Base::foo, at this point Base::foo only declares undefined. Most compilers can be identified when they are linked.

1 #include <iostream>
 2 
 3 class base
 4 {
 5 public:
 6     Base () {Foo ();}   < possible results: Compile warning, link error, run-time error
 7 
 8     virtual void Foo () = 0;
 9};
2 
class Derive:public base
{public
:     Derive (): Base (), M_pdata (new int ()) {}
  15     ~derive () {delete m_pdata;}     virtual void Foo ()         std::cout << *m_pdata << Std::endl;
Private:     int* m_pdata;
};
The 
int main ()
{     base* p = new Derive ();     delete p;
return     0;
30}

If the compiler can recognize this error invocation when compiling or linking, then our chances of making a mistake are greatly reduced. There are some less intuitive cases (see the code below), the compiler is not able to determine. In this case, it can generate an executable file, but an error occurs when the program runs.

1 #include <iostream>
 2 
 3 class base
 4 {
 5 public:
 6     Base () {subtle ();}   < Run-time error (pure virtual function call)
 7 
 8     virtual void Foo () = 0;
 9     void Subtle () {Foo ();}
Ten}; 
class Derive:public Base {public
:     Derive (): Base (), M_pdata (new int (2)) {}
  16     ~derive () {delete m_pdata;}     virtual void Foo ()         std::cout << *m_pdata << Std::endl;
Private:     int* m_pdata;
};
The 
int main ()     base* p = new Derive ();     delete p;
return     0;
31}

From the compiler developer's point of view, how to achieve the above "characteristics".

My guess is to make a fuss over the binding of a virtual function table address: Bind the virtual function table address of the current class to the object when the constructor of the current class (the class being constructed) is invoked. When the base class is partially constructed, the "current class" is the base class, which is base, that is, when the function body of the base::base () is called, the base's virtual function table address is bound to the object. When the function body of the derive::D erive () is called, the derive virtual function table address is bound to the object, so the final object binds the derive virtual function table.

This way the compiler becomes natural when it is processed. Since each class does not have to care about whether there are other classes derived from itself when it is constructed, instead of being concerned about whether or not you derive from other classes, you bind your own virtual function table address to the current object (typically the first 4 bytes in the object's memory space) by following a unified process, before the execution of its own constructor. Because the object is constructed from the most basic class part (for example, A<-b<-c,a is the most basic class, C is the most derived class) begins to construct, one layer to the outside constructs the Intermediate class (B), finally constructs the most derived class (C), therefore the final object binding is naturally the most derived class's virtual function table.

That is, the virtual function table of an object is constantly changing during the construction of the object, is bound once when the base class part (base) is constructed, and the derived class part (derive) is then rebind once. The virtual function call in the base class constructor calls the function according to the normal virtual function call rule, and automatically invokes the virtual function of the base class version, because the object binds to the virtual function table of the base class at this time.

The following is a program written in Visual Studio2010 under WIN7 to verify that the "object's virtual function table is constantly changing in the course of the object being constructed."

This program does three things in the constructor of the class: 1. Print the address of this pointer; 2. Print the address of the virtual function table; 3. Call virtual functions directly through a virtual function table.

The this pointer is printed to indicate that the derive object is created, whether executing base::base () or executing derive::D erive (), which constructs the same object, so the two-time printed this pointer must be equal.

The address of the virtual function table is printed to show that the address of the virtual function table is changed during the creation of the derive object, so the address of the virtual function table printed two times must be unequal.

The virtual function is invoked directly through the function table, just to indicate that the correct virtual function table address is actually printed earlier, so the 19th line of base::base () prints Base, and derive: The 43rd line of the:D erive () prints derive.

Note: This code is compiler-related, because the address of a virtual function table is not necessarily the first 4 bytes stored in the object, this is determined by the compiler's implementation details, so this code may not work properly in a different compiler, using visual Studio2010.

 1 #include <iostream> 2 3 class base 4 {5 Public:6 base () {Printbase ();}
7 8 void Printbase () 9 {std::cout << "address to Base:" << this << Std::endl;
11 12//Virtual table address exists in the object memory space of the first 4 bytes int* vt = (int*) * (int*) * ();
Std::cout << "Address of Base Vtable:" << vt << Std::endl;
15 16//The Foo function is called through VT to prove that VT points to a virtual function of Std::cout << "Call Foo by VT->";
-Void (*pfoo) (base* const) = (void (*) (base* const)) vt[0];
(*pfoo) (this);
Std::cout << Std::endl;
virtual void Foo () {std::cout << "Base" << Std::endl;} 25}; Class Derive:public Base {public:30 derive (): base () {printderive ()} to void Printderive (
) {std::cout << "address of Derive:" << this << Std::endl; 35 36//virtual table address exists in the object memory space of the first 4 bytes int* VT= (int*) * ((int*) this);
Std::cout << "Address of derive Vtable:" << vt << Std::endl;
39 40//The Foo function is called through VT to prove that VT points to the virtual function table Std::cout << "Call Foo by VT->";
(*pfoo) (base* const) = (void (*) (base* const)) vt[0];
(*pfoo) (this);
Std::cout << Std::endl;
$ virtual void Foo () {std::cout << "derive" << Std::endl} 49}; $ int main () base* p = new derive (); Delete p. 0; 56}

The results of the output are the same as expected:

1 Address of Base:002e7f98
2 address of base vtable:01387840
3 Call Foo by VT-> Base
4 
5 Address of Derive:002e7f98
6 Address of derive vtable:01387834
7 call Foo by VT-> derive

Calling a virtual function in a destructor is the same as calling a virtual function in a constructor.

The invocation of a destructor is the opposite of the order in which the constructor is called, starting with the destructor of the most derived class. That is, when the destructor of a base class executes, the destructor of the derived class has already been executed, and the member data in the derived class is considered invalid. Assuming that a virtual function call in a base class can invoke a virtual function that derives from a derived class, the virtual function of the derived class accesses some data that has been "invalid", with the same problem as accessing some uninitialized data. In the same way, we can think that in the process of deconstruction, the virtual function table is also changing.

Adding the above code to the destructor call, and slightly modifying it, validates this:

 1 #include <iostream> 2 3 class base 4 {5 Public:6 base () {Printbase ();}
 7 Virtual ~base () {printbase ();}
8 9 void Printbase () std::cout << "address to Base:" << this << Std::endl;
12 13//virtual table address exists in the object memory space of the first 4 bytes int* vt = (int*) * ((int*) this);
Std::cout << "Address of Base Vtable:" << vt << Std::endl;
16 17//The Foo function is called through VT to prove that VT points to a virtual function of the table Std::cout << "Call Foo by VT->";   void (*pfoo) (base* const) = (void (*) (base* const)) vt[1];
< note that the index becomes 1, because the destructor is defined before Foo (*pfoo) (this);
Std::cout << Std::endl;
virtual void Foo () {std::cout << "Base" << Std::endl;} 26}; Class Derive:public Base {public:31 derive (): base () {printderive ();} virtual ~derive () {P Rintderive (); The void Printderive () Std::cout << "Address of the derive:" << this << Std::endl;
37 38//virtual table address exists in the object memory space of the first 4 bytes int* vt = (int*) * ((int*) this);
Std::cout << "Address of derive Vtable:" << vt << Std::endl;
41 42//The Foo function is called through VT to prove that VT points to a virtual function table Std::cout << "Call Foo by VT->";   The Void (*pfoo) (base* const) = (void (*) (base* const)) vt[1];
< note that the index has become 1, because the destructor is defined before Foo (*pfoo) (this);
Std::cout << Std::endl;
virtual void Foo () {std::cout << "derive" << Std::endl} 51}; 0 int Main () {base* p = new derive (); Delete P. 58}

The following is the print result, which shows that construction and destructor are two processes in reverse order:

1 Address of Base:001e7f98
 2 address of base vtable:01297844
 3 Call Foo by VT-> Base
 4 
 5 Address of  Derive:001e7f98
 6 Address of derive vtable:01297834
 7 call Foo through VT-> derive
 8 
 9 address of derive: 001e7f98 of
derive vtable:01297834 call
Foo through VT-> derive address of 
base:00 1e7f98 to
base vtable:01297844 call
Foo by VT-> Base

Final conclusion:

1. Do not call virtual functions in constructors and destructors, because virtual function calls in this case do not call virtual functions of the outer derived class (reference: http://www.artima.com/cppsource/nevercall.html, http:// www.parashift.com/c%2B%2B-faq-lite/strange-inheritance.html#faq-23.5).

2. The virtual function table address of an object changes with the construction and deconstruction of some classes during the construction and destructor of the object, which should be related to the compiler implementation.

Note: The above discussion is based on simple single inheritance, with some details on multiple inheritance or virtual inheritance.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.