Three major features of C++ in detail - the underlying principle of polymorphism

Table of contents

One, the principle of polymorphism

1.1 Virtual function table

1.2 The underlying implementation of virtual function rewriting (covering)

1.3 The storage location of the new virtual function address of the subclass

1.4 Virtual table storage location

 1.5 The principle of polymorphism

1.6 Dynamic binding and static binding

Second, multiple inheritance

2.1 Virtual function table of multiple inheritance

 2.2 The storage location of the new virtual function address of the subclass

2.3 Why are the virtual function addresses rewritten in the two virtual tables different?

 Summarize


Preamble

The previous article mainly talked about the basic content and use of polymorphism. This article will lead you to understand the underlying principles of polymorphism. There are many experiments in this article. It is recommended that you can experiment with it after reading it. It will definitely be accepted. Goods are plentiful.

One, the principle of polymorphism

1.1 Virtual function table

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

	int _a;
};

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

	int _b;
};
int main()
{
	cout << sizeof(Person) << endl;
	return 0;
}

Veterans of the above code can calculate the size of the Person space?

The answer came out, it is 8, we can look at the size of the normal non-virtual function

 In the above picture, we found that the normal size may be 4 as most old irons calculate, while the one with virtual functions is 8, so where are the extra 4 bytes used? We can debug and take a look

 After debugging and observation, we found that there is an extra pointer _vfptr in front of the object, so what is this _vfptr called? This pointer is called the virtual function table pointer (Virtual Function Pointer), which points to the virtual function table. At least one virtual function table pointer points to the virtual function table in a class containing virtual functions, and the address of the virtual function will be stored in the virtual function table . So what is in the subclass table, let's continue to look down.

Observing the above, we can find that the virtual function tables pointed to by p and s are different, and the two are independent. The virtual function table of the subclass stores the address of the rewritten virtual function, which is also polymorphism. Therefore, the second important condition for polymorphic implementation must be to call virtual functions through the pointer or reference of the parent class . Everyone must understand that, taking the subclass as an example, the pointer or reference of the parent class slices the subclass. Therefore, pointers and references still point to the original space, and therefore _vfptr stores virtual functions rewritten by subclasses, so that the virtual function calls of subclasses and parent classes can be clearly distinguished .

Conversely, why can't it be called by value? If you call by value, you need to use assignment overloading. Normally, the virtual table will not be copied, so it will always be the virtual table of the parent class , and polymorphism cannot be realized. But if we want to copy the virtual table, such polymorphism It may be realized, but it will cause confusion . For example, in the following situation, as a parent class object, is the vtable of p a parent class or a child class? It will become a subclass, is this normal? A parent class object uses a subclass virtual table, which is obviously abnormal.

int main()
{
	Person p;
	Student s;

	p = s;

	return 0;
}

Next, let's take a look at why the rewriting of virtual functions can also be called coverage

1.2 The underlying implementation of virtual function rewriting (covering)

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

	virtual void Func2()
	{
		cout << "Func2" << endl;
	}

	int _a=1;
};

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

	int _b=2;
};

int main()
{
	Person p;
	Student s;

	return 0;
}

 Let's take the above code as an example. In the above example, we can see that the subclass Studnet only rewrites the Func1 virtual function of the parent class Person, so let's enter the debugging mode to see what the virtual tables of Student and Person are. different.

By observing the above figure, we found that after rewriting Func1 in the subclass , the virtual function addresses of Func1 in the parent virtual table and Func1 in the subclass virtual table are different, while the Func2 subclass is not rewritten. The virtual function address of Func2 in the parent virtual table and the subclass virtual table is the same .

This is because after the subclass inherits the parent class, the subclass rewrites Func1, so the original Func1 of the parent class in the function virtual table is overwritten by the virtual function address of Func1 rewritten by the subclass .

Therefore, the rewriting of virtual functions is also called coverage. Coverage refers to the coverage of virtual functions in the virtual table. Rewriting is called syntax, and coverage is called the underlying principle .

1.3 The storage location of the new virtual function address of the subclass

After talking about the principle of rewriting, let's discuss where the new virtual functions of subclasses are stored? Is it stored in the virtual table of the parent class or another virtual table is created?

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


	int _a=1;
};

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

	virtual void Func2()
	{
		cout << "Func2" << endl;
	}

	int _b=2;
};

int main()
{
	Person p;
	Student s;

	return 0;
}

Let's take the above code as an example to explain this problem. In the above code, the subclass object Studen rewrites the Func1 of the parent class object Person. In addition, we wrote a new virtual function Func2 in the subclass. This is to observe the storage location of Func2.

We can debug first to observe whether Func2 is stored in the virtual table, so isn't Func2 stored? This is impossible, because Student may also become another parent class, so it is impossible for its new virtual function not to be stored.

 

 By observing the monitoring window, we found that only Func1 is stored in the virtual table. Is this true? To know that the monitoring window has been processed, we can observe the memory window again

 

 Observing the memory window, we found that the problem is not so simple. The first line is the address of Func1, and the address of the second line is very close to Func1. So is it possible that this is also the address of a virtual function? What is the address of Func2?

Next, let's write a small program that prints the virtual table to prove it

 

 The print result is as follows

 Observing the printed results, we found that the address of the second line is indeed the address of the newly written virtual function Func2 of the Student class.

Therefore, we conclude that although the newly written virtual function of the subclass object cannot be seen in the monitoring window, it is indeed stored in the virtual table .

 Then we have another question, where is the virtual table stored?

1.4 Virtual table storage location

The memory space is roughly divided into the following areas. Veterans can guess which area the virtual table is stored in.

 The method we use to experiment here is to write and create four variables, store them in the stack, heap, static area, and constant area respectively, and then take their addresses, and compare their addresses with the addresses of the virtual table to infer the virtual table. The location where the table is stored .

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


	int _a = 1;
};

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

	virtual void Func2()
	{
		cout << "Func2" << endl;
	}

	int _b = 2;
};

int main()
{
	int i;//栈
	int* ptr = new int;//堆
	static int a = 0;//静态区
	const char* b = "cccccccccc";//常量区

	Person p;
	Student s;

	printf("栈对象:%p\n", &i);
	printf("堆对象:%p\n", ptr);
	printf("静态区对象:%p\n", &a);
	printf("常量区对象:%p\n", b);
	printf("p虚函数表:%p\n", *((int*)&p));
	printf("s虚函数表:%p\n", *((int*)&s));


	return 0;
}

The test results are as follows

Through experiments, we found that the storage location of the virtual function table is very close to the constant area

It can be seen from this that the location where the virtual function table is stored is the same as the location where the virtual function is stored in the code segment, that is, the constant area.

 1.5 The principle of polymorphism

We have talked so much above. So what is the principle of polymorphism?

In fact, the fundamental principle of polymorphism implementation is to declare the existence of polymorphism through virtual, generate virtual table pointers, and manage virtual functions . If the access is a virtual function, find the actual pointed entity through the pointer/reference, and obtain the virtual table in the entity. Pointer, access the virtual table through the virtual table pointer, find the virtual function pointer to be executed in the virtual table, and execute the specific function behavior through the virtual function pointer.

1.6 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 the specific function, also known as dynamic polymorphism .

Second, multiple inheritance

The above is all about single inheritance as an example. Next, let’s talk about the virtual function table of multiple inheritance.

2.1 Virtual function table of multiple inheritance

Before talking about the virtual function table of multiple inheritance, you can think about a question, how many virtual tables are there in the subclass of multiple inheritance?

 

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

	int _a=1;
};
class Base2
{
public:
	virtual void Func1()
	{
		cout << "Base2:Func1" << endl;
	}
	virtual void Func2()
	{
		cout << "Base2:Func2" << endl;
	}

	int _b=2;
};

class Son :public Base1, public Base2
{
public:
	virtual void Func1()
	{
		cout << "Son:Func1" << endl;
	}

	int _c = 3;
};

int main()
{
	Son s;
	return 0;
}

Let's take the above code as an example to look at the memory model of multiple inheritance

 Through observation, we found that there are two virtual function tables in multiple inheritance, and the memory model is as follows

 2.2 The storage location of the new virtual function address of the subclass

In the case of single inheritance above, the new virtual function of the subclass is stored in the virtual table of the parent class, but this multiple inheritance has two parent classes, so where does the address of the new virtual function exist?

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

	int _a=1;
};
class Base2
{
public:
	virtual void Func1()
	{
		cout << "Base2:Func1" << endl;
	}
	virtual void Func2()
	{
		cout << "Base2:Func2" << endl;
	}

	int _b=2;
};

class Son :public Base1, public Base2
{
public:
	virtual void Func1()
	{
		cout << "Son:Func1" << endl;
	}


	virtual void Func3()
	{
		cout << "Son:Func3" << endl;
	}


	int _c = 3;
};

typedef void(*VF_PTR)();
//typedef void(*)() VF_PTR;上面定义的效果就和这个类似,
//但是由于是函数指针,所以只能用上面的定义方式
void print(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("[%d]:%p->", i, table[i]);
		VF_PTR f = table[i];//保存函数地址
		f();//对函数地址调用
	}
	cout << endl;
}

int main()
{
	Son s;


	return 0;
}

Here we can verify by printing the virtual table before. But there is a problem here, that is, how to find the pointer of Base2? The method we take here is to slice and slice Base2, and the pointer will automatically offset to the position of Base2.

 From the above results, we can find that in multiple inheritance, the address of the newly created virtual function of the subclass is stored in the virtual table of the first parent class .

From the above experiment results, I don’t know if the veterans have found a problem, that is, we rewritten Func1 in the subclass, but the address of Func1 in the two virtual tables of Base1 and Base2 is different. Why?

2.3 Why are the virtual function addresses rewritten in the two virtual tables different?

We can slice Base1 and Base2 separately and call Func1 to observe their respective compilations to see their respective trends.

 Compile the experimental results as follows

 By observing the results, we found that calling Func1 by ptr1 is a direct call, and the process of calling Func1 by ptr2 is indeed so difficult, especially there is a sub 8 in the middle, why is this?

In fact, calling Func1 is ultimately called by *this pointer. Note that *this pointer points to the Son class. The reason why ptr1 can be called directly is because the place pointed to by ptr1 is the same as *this, so it can be directly Call Func1, but ptr2 does not work. ptr2 points to the position of Base2, which does not overlap with the position pointed to by *this. It will be encapsulated, adjust the position pointed to by ptr2 to the position pointed to by *this, and then call Func1. That is to say, there is a -8 command in the middle to play this role .

 

 Summarize

The above is all the content of the polymorphic principle part. The above experiments are completed by bloggers. I hope that the iron people can gain something

Guess you like

Origin blog.csdn.net/zcxmjw/article/details/129978980