[C ++]-Static binding and dynamic binding caused by virtual functions

One, virtual function

1. Classes with added virtual functions for the first time

In the last blog, you have learned about inheritance. In this article, we first write an inheritance.

#include<iostream>
#include<typeinfo>
using namespace std;
class Base
{
public:
	Base(int data = 10) :ma(data) { }
	void show() { cout << "Base::show()" << endl; }
	void show(int) { cout << "Base::show(int)" << endl; }
protected:
	int ma;
};
class Derive :public Base
{
public:
	Derive(int data = 20):Base(data), mb(data) {}
	void show() { cout << "Derive::show()" << endl; }
private:
	int mb;
};
int main()
{
	Derive d(50);
	Base* pb = &d;
	
	pb->show(); 
	pb->show(10);
	
	cout << sizeof(Base) << endl;
	cout << sizeof(Derive) << endl;
	
	cout << typeid(pb).name() << endl;
	cout << typeid(*pb).name() << endl;
	return 0;
}

The results are as follows:
Insert picture description here

Observe the above results, combined with the knowledge of the previous blog. We can analyze the reason.
Because pb is a pointer to a base class, the first call to the show constructor without parameters of the base class and then the show constructor with parameters is called.
Because the base class has only one member variable ma, the size is 4 bytes, and the derived class inherits the inherited ma, plus its own member variable mb, so the size is 8 bytes
because pb is the base class pointer, So his type is class Base *. class Base The type of pointer dereference, similar to the type pointed to by the pointer

The above is our conventional inheritance method, but if we add the virtual keyword in front of the two show () methods of the base class, the following code, what will happen to the running result?

Let's take a closer look ~

virtual void show() { cout << "Base::show()" << endl; }
	virtual void show(int) { cout << "Base::show(int)" << endl; }

(1) A virtual function is defined in a class, then the compiler needs to generate a unique vftable (virtual function table) for this type during the compilation stage. The main content stored in the table is the RTTI pointer and virtual function address. When the program runs , Each virtual function table will be loaded into the .rodata area

The following figure is a virtual function table corresponding to the Base type:
Insert picture description here
In order to understand this virtual function table, let's first talk about the meaning of
RTTI : run-time type ifformation type information at runtime
** 0: ** is vfptr The offset in the object memory, because the priority of vfptr is very high, so it generally exists in the first four bytes, so the offset is generally 0

(2) A virtual function is defined in a class, then the objects defined by this class, at runtime, at the beginning of the memory, store a vfptr virtual function pointer, pointing to the corresponding type of virtual function table vftable, a type defined n Objects, their vfptr points to the same virtual function table

For example, the following figure shows the memory maps of objects b1 and b2 defined by the Base class:
Insert picture description here
From the above figure, we can see
( 3) The number of virtual functions in a class does not affect the memory size of the object (vfptr), which affects the virtual Function table size

As can be seen from our code, now we have defined the show method in the base class as a virtual function, but did not change the show method in the derived class, then we use the derived class to define two objects d1 and d2. Their memory map is as follows :
Insert picture description here
From this we can draw
(4) If the method in the derived class and a method inherited from the base class have the same return value, function name, and parameter name, and the method of the base class is a virtual virtual function, then the derived class This method is automatically processed into a virtual function

Finally, let's analyze the change of pointer type ~

cout << typeid(pb).name() << endl;
cout << typeid(*pb).name() << endl;

First look at the type of pb to get it as Base, and then see if there are virtual functions in Base

  1. If Base has no virtual function, * pb recognizes the type at compile time * pb == Base type

  2. If Base has a virtual function, * pb identifies the RTTI type at runtime. The RTTI type is stored in the virtual function number table, where pb is d (vfptr) pointing to the virtual function table of its derived class, the virtual function table RTTI type is class Derive

    So after having a virtual function, the results are as follows:
    Insert picture description here

2. Which functions cannot be implemented as virtual functions

Before answering this question, we must first clarify the dependency of the virtual function

  1. Virtual functions can generate addresses and store them in vftable
  2. The object must exist, because the object exists only vfptr can find vftable to find the virtual function address

With the above dependence characteristics, we can easily give answers to questions.
The first kind: constructor
can not add virtual in front of the constructor because the constructor is generated after the construction is completed.
Features: In the
constructor (any function called, are statically bound), dynamic binding will not occur when the virtual function is called The
constructor of the derived class object first calls the constructor of the base class before calling the constructor of the derived class

The second kind: static static member method
because its method call does not depend on the object

3. Virtual destructor

From the above explanation, we know that the constructor can not become a virtual function, but the destructor is possible.
Because the object exists when it is called

We implement the following code:

class Base 
{
public:
	Base(int data) :ma(data) { cout << "Base()" << endl; }
	~Base() { cout << "~Base()" << endl; }
	virtual void show() { cout << "Base::show()" << endl; }
protected:
	int ma;
};
class Derive :public Base
{
public:
	Derive(int data)
	   :Base(data), mb(data)
	{
		delete ptr;
		cout << "Derive()" << endl;
	}
	~Derive()
	{
		cout << "~Derive()" << endl;
	}
private:
	int mb;
	int* ptr;
};
int main()
{
	Base* pb = new Derive(10);
	pb->show();
	delete pb;
	return 0;
}

The results of the operation are as follows:
Insert picture description here
careful observation, we will find that he is wrong , because the derived class object does not have a destructor
In the derived class, we have a ptr pointing to external memory, but the destructor of the derived class has not been called.

Because the call of pa-> Base Base :: ~ Base to the destructor is static binding. Only the destructor of the base class is called and the destructor of the derived class is not called call Base :: ~ Base

Solution:
Implement the destructor of the base class as a virtual function class. The destructor of the virtual class is a virtual virtual function, then the destructor of the derived class automatically becomes a virtual function. It can be imagined as an overlay of a function of the same name. The call to the destructor is dynamic binding.

The results are as follows:
Insert picture description here
From this, we can summarize when the base class destructor must be implemented as a virtual function-the base class pointer (reference) points to the new derived class object on the heap, delete pb (base class Pointer ), when it calls the destructor, dynamic binding must occur or the destructor of the derived class cannot be called

Second, dynamic binding and static binding

1. The specific implementation of dynamic binding and static binding

With the above summary of adding virtual functions to a class, what impact it has on this class. Next, let's take a good look at what happens when the statement pb-> show (); is executed ~

Case 1: Static binding
pb-> Base Base :: show If show is an ordinary function , static binding is performed. The instructions are:

  • call Base::show(01612DAh)

Case 2: Dynamic binding
pb-> Base Base :: show If show is a virtual function , the instructions for dynamic (runtime) binding (function call)
execution are:

  • mov eax, dword ptr [pb]
    put the address of the virtual function table of the first four bytes of the object into the register
  • mov ecx, dword ptr [eax]
    put the four bytes of eax memory (show method of derived class) into the ecx register
  • call ecx (virtual function address)
    call is a register, it is not possible to determine which function is called during the compilation phase, and only when running is to know what address is placed in the register

2. Virtual functions and dynamic binding

Through the above discussion of which functions cannot be implemented as virtual functions, we can easily find that ** not the call of a virtual function must be dynamic binding, ** because the virtual function is called in the class constructor but is statically bound

So, let's discuss what kind of situation is dynamic binding ~
First, let's implement such an inherited code:

class Base
{
public:
	Base(int data=0) :ma(data) {  }
	virtual void show() { cout << "Base::show()" << endl; }
protected:
	int ma;
};
class Derive :public Base
{
public:
	Derive(int data=0)
		:Base(data), mb(data)
	{
	}
	void show() { cout << "Derive::show()" << endl; }
private:
	int mb;
};

Case 1: The virtual function is called with the object itself, which is statically bound
. The following call is implemented in the main function

int main()
{
	Base b;
	Derive d;
	b.show();//call Base::show
	d.show();//call Derive::show
	return 0;
}

This achieves static binding, because if dynamic binding is implemented, the corresponding virtual function pointer is used to access the corresponding virtual function table to obtain the virtual function address, which is the same as static binding

Case 2: Pointer call virtual function is dynamically bound

Implement the following call in the main function

int main()
{
	Base b;
	Derive d;
	Base* pb1 = &b;
	pb1->show();

	Base* pb2 = &d;
	pb2->show();
	return 0;
}

Case 3: Calling virtual function by reference variable is dynamic binding

Implement the following call in the main function

int main()
{
	Base b;
	Derive d;
	Base& rb1 = b;
	rb1.show();
	Base& rb2 = d;
	rb2.show();
	return 0;
}

Case 4: The function is called by a pointer or reference variable, and dynamic binding occurs

Implement the following call in the main function

int main()
{
	Base b;
	Derive d;
	Derive* pd1 = &d;
	pd1->show();
	Derive& rd1 = d;
	rd1.show();
	return 0;
}

Summary: If the virtual function is not called through a pointer or reference variable, it is static binding

Published 98 original articles · won praise 9 · views 3664

Guess you like

Origin blog.csdn.net/qq_43412060/article/details/105224354