[C++] Virtual function table & principle of polymorphism & dynamic binding and static binding

Sort out the knowledge of virtual function table, polymorphism principle, dynamic and static binding

Table of contents

1. Virtual function table

Second, the principle of polymorphism

3. Dynamic binding and static binding


1. Virtual function table

Before learning the principle of polymorphism, we need to understand the concept of virtual function table 

Let's take a look at the following code

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

Through testing, we found that the b object is 8 bytes. In addition to the _b member, there is an additional __vfptr pointer placed in front of the object (note that some platforms may put it at the end of the object, which is related to the platform). The pointer in the object we It is called a virtual function table pointer (v stands for virtual, f stands for function) . A class containing virtual functions has at least one virtual function table pointer, because the address of the virtual function must be placed in the virtual function table, and the virtual function table is also referred to as the virtual table .

The relationship between the virtual function table pointer and the virtual table will be explained in detail later.

 

Let's first learn the knowledge of virtual tables. What is the table (virtual table) in this derived class? What's in it? Let's go on to analyze

// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};

class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}

Through observation and testing, we found the following problems:

1. There is also a virtual table pointer in the derived class object d. The d object is composed of two parts, one part is the member inherited from the parent class, including the virtual table pointer and the member variable of the parent class, and the other part is its own member.

2. The virtual table of the base class b object is different from that of the derived class d object. Here we find that Func1 has been rewritten, so the rewritten Derive::Func1 is stored in the virtual table of d, so the rewriting of the virtual function Also called coverage , coverage refers to the coverage of virtual functions in the virtual table. Rewriting is called syntax, and coverage is called principle layer.

3. In addition, Func2 is inherited as a virtual function, so it is put into the virtual table, and Func3 is also inherited, but it is not a virtual function, so it will not be put into the virtual table.

4. The essence of the virtual function table is an array of pointers storing virtual function pointers . Generally, a nullptr is placed at the end of this array.

5. Summarize the virtual table generation of the derived class: a. First copy the content of the virtual table in the base class to the virtual table of the derived class; b. If the derived class rewrites a virtual function in the base class, use the derived class The virtual functions of the class itself cover the virtual functions of the base class in the virtual table; c. The newly added virtual functions of the derived class are added to the end of the derived class virtual table according to the order of declaration in the derived class.

6. There are still a few confusing questions here: Where does the virtual function exist? Where does the virtual table exist? Does the virtual function exist in the virtual table? Does the virtual table exist in the object? Answer: Note that the virtual table stores virtual function pointers, not virtual functions. Virtual functions are the same as ordinary functions, and they all exist in the code segment, but their pointers are stored in the virtual table . In addition, what is stored in the object is not a virtual table, but a virtual table pointer . So where does the virtual table exist? In fact, if we verify it, we will find that the virtual table exists in the code segment under vs.

[Summary] Object -> virtual table pointer -> virtual table ( pointer array of virtual function pointers ) -> virtual function pointer -> virtual function.

 
 


Second, the principle of polymorphism

Through the above study, we have a general understanding of the virtual table. So what is the principle of polymorphism? What is the relationship with virtual table? Next, let's take a look at the following legend. Here, the Func function passes Person::BuyTicket called by Person, and Student::BuyTicket is called by Student.

code show as below

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

Running the above code and opening the watch window, we can get

1. Observing the red arrow in the figure below, we can see that when p points to the mike object, p->BuyTicket finds the virtual function Person::BuyTicket in mike’s virtual table.

2. Observe the blue arrow in the figure below and we can see that when p points to the johnson object, p->BuyTicket finds the virtual function Student::BuyTicket in john’s virtual table.

3. In this way, when different objects complete the same behavior, they show different forms.

4. Conversely, if we want to achieve polymorphism, there are two conditions, one is virtual function coverage, and the other is to call a virtual function with a pointer or reference to an object (base class). Interested friends can think about why? The answer is that virtual function coverage can output different results, and the following process can be accurately completed by calling virtual functions through base class pointers or references: object -> virtual table pointer -> virtual table -> virtual function pointer -> virtual function.

5. Through the following assembly code analysis, we can conclude that the function call after satisfying polymorphism is not determined at compile time, but is found in the object after running. Function calls that do not satisfy polymorphism are confirmed at compile time.

//多态调用
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person mike;
	Func(&mike);
	mike.BuyTicket();

	return 0;
}

Shown below is the disassembly of the polymorphic call 

// 以下汇编代码中与问题不相关的都被去掉了
void Func(Person* p)
{
	...
	p->BuyTicket();
	// p中存的是mike对象的指针,将p移动到eax中

	001940DE  mov         eax, dword ptr[p]
	// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx

	001940E1  mov         edx, dword ptr[eax]
	// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax

	00B823EE  mov         eax, dword ptr[edx]

    001940EA  call        eax
    // call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来
	//以后到对象的中取找的。
	
    001940EC  cmp         esi, esp
}

Shown below is the disassembly of an ordinary function call 

//普通函数调用
int main()
{
	...
	// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数
	// 普通函数的调用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
	mike.BuyTicket();
	00195182  lea         ecx, [mike]
	00195185  call        Person::BuyTicket(01914F6h)
	...
}


3. Dynamic binding and static binding

1. Static binding, also known as early binding (early binding), determines the behavior of the program during program compilation, also known as static polymorphism, such as: function overloading.

2. Dynamic binding, also known as late binding (late binding), is to determine the specific behavior of the program according to the specific type obtained during the running of the program, and call specific functions, also known as dynamic polymorphism. [Most of the polymorphism we come into contact with is dynamic polymorphism].

3. The disassembly code shown in Section 2 The assembly code is a good explanation of what is static (compiler) binding and dynamic (runtime) binding.

The function call after satisfying the polymorphism is not determined at compile time, but is found in the object after it is run, so it is a dynamic binding. The general function call, which determines the behavior of the program during program compilation, is statically bound.


The above is all the content of this article. If it is helpful to you, you may wish to like, bookmark, and follow . Thank you for reading.

Guess you like

Origin blog.csdn.net/Captain_ldx/article/details/130009820
Recommended