[C++] polymorphism with multiple inheritance

foreword

Although multiple inheritance is not recommended, some scenarios are still very suitable for multiple inheritance. At the same time, although this part of knowledge will not be used in practice, we can also learn the power of the language from it. C++, as the big brother at the time, of course, has a lot of pitfalls because of the cutting-edge. We can also solve these problems from the father of C++ Learn some ideas
, so let's start today's study

insert image description here

1. Multi-inherited virtual function table

We first build a multiple inheritance model

There is a Base1 parent class and a Base2 parent class. Both parent classes have two virtual functions fun1 and fun2.
Derive inherits these two classes publicly, but Derive only rewrites fun1 and adds a new virtual function fun3.
Question: Derive有几个虚表? 新增的虚函数放在哪?

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

	virtual void func2()
	{
    
    
		cout << "Base1::func2()" << endl;
	}


private:
	int _b1;
};

class Base2
{
    
    
public:

	virtual void func1()
	{
    
    
		cout << "Base2::func1()" << endl;
	}

	virtual void func2()
	{
    
    
		cout << "Base2::func2()" << endl;
	}


private:
	int _b2;
};

class Derive :public Base1,public Base2
{
    
    
public:

	virtual void func1()
	{
    
    
		cout << "Derive::func1()" << endl;
	}

	virtual void func3()
	{
    
    
		cout << "Derive::func3()" << endl;
	}

private:
	int _d1;
};

First of all, we can get the answer to the first question through the monitoring window
insert image description here
. We can see that there are two virtual tables inside Derive, one inherits from Base1 and the other inherits from Base2. All Derives have two virtual tables, but we still cannot see the newly added func3 through the monitoring window. At this time, we can also use the function of printing the virtual table used in the polymorphic conclusion proof of the previous blog.

typedef void(*VF_PTR)();
void Print(VF_PTR table[])
{
    
    
	for (int i = 0; table[i]; i++)
	{
    
    
		printf("[%d]:%p->", i, table[i]);
		VF_PTR f = table[i];
		f();
	}
	cout << endl;
}

I won't explain it here, and those who are interested can check the previous blog.

But the oncoming problem is that when printing the first virtual table, we can directly get the first four bytes of the object, but what about the second virtual table? Is it necessary to offset it? How much is the offset? Why is it biased?

We can directly access the first virtual table 前四个字节to get the virtual table pointer

Print((VF_PTR*)(*(int*)(&d)));

In the second chapter of the virtual table, we need to obtain the offset pointer. How much is the offset? To offset the size of Base1, note that we need to convert it to char* first, so that the access range is 1 byte, plus the size of Base1 is the correct offset

Print((VF_PTR*)(*(int*)(  (char*)&d  +sizeof(Base1) )));

insert image description here
We saw,新增的虚函数,是加到第一张虚表的后面的

In addition, in addition to manually offsetting the pointer, we can also do this. The parent class pointer points to the subclass, will be sliced, and will automatically point to the parent class part of the subclass, so that the offset of the pointer is automatically realized.
insert image description here

2. Overridden virtual functions

But take a closer look, we will find a problem
insert image description here
Here we print out the address of the virtual function pointed to by the function pointer through the virtual table, and at the same time take out the function pointer and call it. Here func1 calls the virtual function rewritten by the subclass, but 这两个虚函数的地址却不一样.
Does this mean that there are two rewritten func1 virtual functions?
The answer is, 没有,只有一份
the following is explained from an assembly point of view

insert image description here
From the two assembly instructions that call func1, we found that, 最终都会跳转到同一个地址的函数so the rewritten func1 function is indeed only 一份, but why the address of func2 is different? And why is there an extra jump in the middle?

We have observed that the assembly jump of Base2 is to execute one more instruction.
insert image description here
ecx是this指针的寄存器
The function of the assembly instruction in this step is 将当前的this指针往上移动8个字节。
because polymorphism is satisfied, the parent class pointer points to the subclass object, but the function of the subclass is called, so it this指针must point to 子类对象的首地址, But the parent class pointer will happen 切片, and the pointer points to 子类的父类部分, so it is necessary 偏转to return to the first address of the subclass object.
So why doesn't Base1 need to deflect back? This is because Base1是先继承的its address is in the subclass 第一部分, soBase1的父类指针刚好指向子类的首地址。

3. Dynamic and static binding

1. Static binding

Static binding, also known as static polymorphism, determines the behavior of the program 前期绑定(早绑定)in the program . 编译期间For example: 函数重载
函数模板本质也是函数重载, why the function template can't 分离编译, is because it needs to be 编译时determined 参数类型, otherwise it won't be generated 函数的地址, and it will be generated at runtime 链接错误.

2. Dynamic Binding

Dynamic binding, also known as late binding (late binding), is in the program 运行期间, according to the specific behavior of the specific 类型determined program, calling a specific function, also known as动态多态。

4. The virtual table and virtual base table of diamond inheritance

This part is supplementary knowledge, you don’t need to master it, because diamond inheritance is not recommended to implement, and polymorphism based on this is not used. Such a model is also complicated, so you don’t need to master it.

We build classes like this

insert image description here
There is a virtual function in A, B and C virtual inherit A, rewrite the virtual function of A, and also have its own new virtual function, D inherits B and C

class A
{
    
    
public:
	virtual void func1()
	{
    
    }
public:
	int _a;
};

class B : virtual public A
{
    
    
public:
	virtual void func1()
	{
    
    }

	virtual void func2()
	{
    
    }
public:
	int _b;
};

class C : virtual public A
{
    
    
public:
	virtual void func1()
	{
    
    }

	virtual void func3()
	{
    
    }
public:
	int _c;
};

class D : public B, public C
{
    
    
public:
	virtual void func1()
	{
    
    

	}
public:
	int _d;
};

int main()
{
    
    
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

According to 菱形虚拟继承the knowledge, in order to solve data redundancy and ambiguity, it will be created 虚基表and stored in B and C 偏移量, and because of virtual inheritance, polymorphism is realized. A,B,C,D中都有虚表
Next, we 内存窗口look at the specific storage structure of the d object
insert image description here

A,B,C的第一个地址都是虚表指针
B,C的第二个地址是虚基表指针

Because B and C rewrite the virtual function of A at the same time, the original virtual tables of B and C should be the function pointers of their own virtual functions, but because they both inherit the virtual functions of A, the function pointers are not in B. and C's respective virtual tables, A的内容会被放在对象的最下面,变为公共部分, so there can only be 一个rewritten virtual functions, which is not appropriate to use at this time, so at this time 要求D必须也重写A的虚函数.

So at this time, in the virtual tables of B and C, there are actually only the function pointers of the newly added virtual functions.
insert image description here

The virtual base tables of B and C are like this.
insert image description here
The first one is ff ff ff fc, which represents -4, which is also an offset. It just moves to the position of the virtual table
. The second offset is to offset to the common part of A. of.

conclusion

This article is a note on some partial knowledge of polymorphism, the mastery requirements are not deep, thank you for reading

If you think this article is helpful to you, you might as well like it to support the blogger, please, this is really important to me.
insert image description here

Guess you like

Origin blog.csdn.net/m0_72563041/article/details/129973185