[C++] The underlying principle of polymorphism (virtual function table)


Preface

1. Virtual function table

Insert image description here
Insert image description here

Through observation and testing, we found that the b object is 8 bytes. In addition to the _b member, there is an additional __vfptr 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's 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

2. Virtual function table in derived classes

1.Principle

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;
}

Insert image description here

1. The virtual table of the base class b object and the derived class d object are different. 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 overwriting is called principle layer.
2. In addition, Func2 is a virtual function after being inherited, so it is put into the virtual table. Func3 is also inherited, but it is not a virtual function, so it is not put into the virtual table.
3. The virtual function table is essentially a pointer array that stores virtual function pointers. Generally, a nullptr is placed at the end of this array.

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’s own The virtual function overrides the virtual function of the base class in the virtual table c. The newly added virtual function of the derived class is added to the end of the derived class virtual table according to the order of declaration in the derived class.

2. Example:

Insert image description here

  1. Observing the red arrow in the figure below, we see that when p points to the mike object, p->BuyTicket finds the virtual function Person::BuyTicket in mike's virtual table.
  2. Observing the blue arrow in the figure below, we see that when p points to the Johnson object, p->BuyTicket finds the virtual function Student::BuyTicket in Johnson's virtual table.
  3. In this way, different objects can show different forms when completing the same behavior.
    Insert image description here

Function calls after satisfying polymorphism are not determined at compile time, but are retrieved from the object after running. Function calls that do not satisfy polymorphism are confirmed at compile time.

3. Storage location of virtual functions

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 .

Shared virtual function table of the same type:
Since the virtual function tables of different objects of the same class are the same, the addresses of the virtual functions they need are the same, so they can share the same virtual function table, which saves memory space. This design can make each object only need a pointer to point to the virtual function table, instead of allocating a separate virtual function table for each object.

4. Virtual function table in single inheritance

class Person {
    
    
	public:
		virtual void Func1() {
    
    cout << "Person::Func1()" << endl;}
		virtual void Func2() {
    
    cout << "Person::Func2()" << endl;}

		int _a = 0;
	};
class Student : public Person {
    
    
  
private:
	virtual void Func3(){
    
    cout << "Student::Func3()" << endl;}
	protected:
		int _b = 1;
};

	int main() {
    
    
		Person p;
		Student s;
		return 0;
	}

We will find that a function pointer of func3 is missing from the virtual function table. We will verify that func3 does exist in the virtual base table of the derived class.
Insert image description here
Each object has its own virtual function pointer , pointing to the corresponding virtual function table. Here the virtual function pointers of the base class and the derived class point to the same address, because the virtual function of the base class is not rewritten in the derived class (Func1 and Func2). When the derived class inherits the base class, the derived class will inherit the base class virtual function table, and add or overwrite its own virtual functions in its virtual function table. If the derived class does not override the virtual function of the base class , then the corresponding virtual function address in the virtual function table of the derived class still points to the virtual function in the base class.
Insert image description here
The virtual function table is essentially a pointer array that stores virtual function pointers. A nullptr is placed at the end of this array.

Idea: Take out the first 4 bytes of the ps and st objects, which are the pointers of the virtual table, and then use the pointers of the virtual table to traverse the virtual function array. If the function pointer of Func3 can be printed out, it means that Func3 is in the virtual function table

typedef void(*FUNC_PTR) ();//FUNC_PTR为函数指针
void PrintVFT(FUNC_PTR* table)//table为函数指针数组
{
    
    
	//虚函数表本质是一个存虚函数
	//指针的指针数组,这个数组最后面放了一个nullpt
	for (size_t i = 0; table[i] != nullptr; i++)
	{
    
       printf("[%d]:%p->", i, table[i]);
		FUNC_PTR f = table[i];
		f();//函数指针指向函数的内存地址,
		//通过调用函数指针,实际上是调用了指向的函数
	}
	printf("\n");
}
	int main() {
    
    
	Person ps;
	Student st;
//1.先取ps的地址,强转成一个int * 的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针(二重函数指针)
	int vft1 = *((int*)&ps);
	PrintVFT((FUNC_PTR*)vft1);
//3.再强转成VFPTR * ,因为虚表就是一个存FUNC_PTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
	int vft2 = *((int*)&st);
	PrintVFT((FUNC_PTR*)vft2);
//5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最
//后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。
		return 0;
	}

Insert image description here

5. Virtual function table in multiple inheritance

Multiple inheritance object virtual function table

Insert image description here

Insert image description here
Insert image description here

There are multiple virtual function tables in multiple inheritance

Unoverridden virtual functions of multiple inheritance derived classes are placed in the virtual function table of the first inherited base class part.
Insert image description here

6. Questions and Answers

  1. Can an inline function be a virtual function?
    Answer: Yes, but the compiler ignores the inline attribute, and this function is no longer inline, because the virtual function must be placed in the virtual table.
    2. Can static members be virtual functions?
    Answer: No, because the static member function does not have a this pointer, and the virtual function table cannot be accessed by calling the type:: member function, so the static member function cannot be placed in the virtual function table.
    3. Can the constructor be a virtual function?
    Answer: No, because the virtual function table pointer in the object is initialized only at the constructor initialization list stage.
    4. At what stage is the virtual function table generated and where does it exist?
    Answer: The virtual function table is generated during the compilation phase, and generally exists in the code segment (constant area).
  2. Is it faster for objects to access normal functions or virtual functions?
    Answer: First of all, if it is a normal object, it is just as fast. If it is a pointer object or a reference object, the ordinary function called is fast, because it constitutes polymorphism, and calling a virtual function at runtime needs to look up in the virtual function table.

Guess you like

Origin blog.csdn.net/m0_74774759/article/details/132163391
Recommended