上一章 简述了虚函数的调用链路,本章主要探索 C++ 各种继承关系的类对象的多态特性。
封装,继承,多态是 C++ 的三大特性,其中多态与继承有密切关系。C++ 语言支持三种继承关系:单一继承,多重继承,虚拟继承:
图片来源:《多型与虚拟》
C++ 的单一继承是指一个类只能从一个父类继承属性和方法。
文字来源:ChatGPT
动态多态的单一继承对象类层次结构相对简单:
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include
class Base {
public:
virtual void vBaseFunc() {}
virtual void vBaseFunc2() {}
virtual void vBaseFunc3() {}
long long m_base_data;
long long m_base_data2;
};
class Base2 : public Base {
public:
virtual void vBaseFunc() {}
virtual void vBase2Func() { std::cout << "Base2::vBase2Func" << std::endl; }
virtual void vBase2Func2() {}
long long m_base2_data;
long long m_base2_data2;
};
class Derived : public Base2 {
public:
virtual void vBaseFunc2() {}
virtual void vBase2Func() { std::cout << "Derived::vBase2Func" << std::endl; }
virtual void vDerivedFunc() {}
virtual void vDerivedFunc2() {}
long long m_derived_data;
long long m_derived_data2;
};
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 5u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))(& _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3
Vtable for Base2
Base2::_ZTV5Base2: 7u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base2)
16 (int (*)(...))Base2::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3
40 (int (*)(...))Base2::vBase2Func
48 (int (*)(...))Base2::vBase2Func2
# Derived 虚表。
Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 9u entries
0 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8 (int (*)(...))(& _ZTI7Derived)
16 (int (*)(...))Base2::vBaseFunc
24 (int (*)(...))Derived::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3
40 (int (*)(...))Derived::vBase2Func
48 (int (*)(...))Base2::vBase2Func2
56 (int (*)(...))Derived::vDerivedFunc
64 (int (*)(...))Derived::vDerivedFunc2
# 类的继承关系
Class Derived
size=56 align=8
base size=56 base align=8
Derived (0x0x7fb058fa8478) 0
# 虚指针指向虚表的位置。
vptr=((& Derived::_ZTV7Derived) + 16u)
Base2 (0x0x7fb058fa8a28) 0
primary-for Derived (0x0x7fb058fa8478)
Base (0x0x7fb058ee87e0) 0
primary-for Base2 (0x0x7fb058fa8a28)
虚函数调用。
int main() {
auto d = new Derived;
std::cout << d << std::endl;
auto b = static_cast<Base2 *>(d);
std::cout << b << std::endl;
b->vBase2Func();
return 0;
}
// 输出:
// 0x13a0010
// 0x13a0010
// Derived::vBase2Func
C++ 支持多重继承,这意味着一个类可以从多个父类继承属性和方法,在 C++ 中,可以使用逗号分隔的方式来指定多个父类。
文字来源:ChatGPT。
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include
class Base {
public:
virtual void vBaseFunc() {}
virtual void vBaseFunc2() {}
long long m_base_data;
long long m_base_data2;
};
class Base2 {
public:
virtual void vBase2Func() {}
virtual void vBase2Func2() { std::cout << "Base2::vBase2Func2" << std::endl; }
long long m_base2_data;
long long m_base2_data2;
};
class Base3 {
public:
virtual void vBase3Func() {}
virtual void vBase3Func2() {}
long long m_base3_data;
long long m_base3_data2;
};
class Derived : public Base, public Base2, public Base3 {
public:
virtual void vBaseFunc() {}
virtual void vBase2Func2() { std::cout << "Derived::vBase2Func2" << std::endl; }
virtual void vBase3Func2() {}
virtual void vDerivedFunc() {}
virtual void vDerivedFunc2() {}
long long m_derived_data;
long long m_derived_data2;
};
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))(& _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
Vtable for Base2
# _ZTV5Base2: vtable for Base2
Base2::_ZTV5Base2: 4u entries
0 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
8 (int (*)(...))(& _ZTI5Base2)
16 (int (*)(...))Base2::vBase2Func
24 (int (*)(...))Base2::vBase2Func2
Vtable for Base3
# _ZTV5Base3: vtable for Base3
Base3::_ZTV5Base3: 4u entries
0 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
8 (int (*)(...))(& _ZTI5Base3)
16 (int (*)(...))Base3::vBase3Func
24 (int (*)(...))Base3::vBase3Func2
Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 16u entries
0 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8 (int (*)(...))(& _ZTI7Derived)
16 (int (*)(...))Derived::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Derived::vBase2Func2
40 (int (*)(...))Derived::vBase3Func2
48 (int (*)(...))Derived::vDerivedFunc
56 (int (*)(...))Derived::vDerivedFunc2
64 (int (*)(...))-24
72 (int (*)(...))(& _ZTI7Derived)
80 (int (*)(...))Base2::vBase2Func
# _ZThn24_N7Derived11vBase2Func2Ev: non-virtual thunk to Derived::vBase2Func2()
88 (int (*)(...))Derived::_ZThn24_N7Derived11vBase2Func2Ev
96 (int (*)(...))-48
104 (int (*)(...))(& _ZTI7Derived)
112 (int (*)(...))Base3::vBase3Func
# _ZThn48_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
120 (int (*)(...))Derived::_ZThn48_N7Derived11vBase3Func2Ev
Class Derived
size=88 align=8
base size=88 base align=8
Derived (0x0x7f4196042348) 0
vptr=((& Derived::_ZTV7Derived) + 16u)
Base (0x0x7f4195f3e840) 0
primary-for Derived (0x0x7f4196042348)
Base2 (0x0x7f4195f3e8a0) 24
vptr=((& Derived::_ZTV7Derived) + 80u)
Base3 (0x0x7f4195f3e900) 48
vptr=((& Derived::_ZTV7Derived) + 112u)
虚表整合。
对象整体布局。由下图可见:
int main() {
auto d = new Derived;
std::cout << d << std::endl;
auto b = static_cast<Base2 *>(d);
std::cout << b << std::endl;
b->vBase2Func2();
return 0;
}
// 输出:
// 0x13db010
// 0x13db028
// Derived::vBase2Func2
# thunk to 跳转原理(汇编)。
0000000000400aba <non-virtual thunk to Derived::vBase2Func2()>:
# rdi 寄存器保存的是 b 指针指向的地址,该地址向低地址偏移 0x18 个字节,
# 也就是 rdi 寄存器保存的是 Derived 内存首位地址,
# 换句话说,将 Derived 的 this 指针作为参数传入 Derived::vBase2Func2 函数。
400aba: 48 83 ef 18 sub $0x18,%rdi
# 调用 Derived::vBase2Func2() 函数。
400abe: eb d0 jmp 400a90 <Derived::vBase2Func2()>
int main() {
Base2* b = new Derived;
delete b;
return 0;
}
多重继承可以让一个类具有多个不同父类的特性,但也可能引发一些问题,比如菱形继承问题。为了解决这个问题,C++ 提供了虚继承和虚基类的概念。虚继承可以解决菱形继承问题,确保只有一个实例的共享基类。
在 C++ 中,虚拟继承(virtual inheritance)是一种特殊的继承方式。它用于解决多重继承中的菱形继承问题。当一个类通过虚拟继承从多个基类继承时,只会保留一个基类的实例,而不会重复继承。这样可以避免菱形继承带来的二义性和冗余。在虚拟继承中,派生类需要使用关键字 “virtual” 来声明基类。
文字来源:ChatGPT
因为继承关系中有共享基类,为了避免共享基类产生多个对象副本浪费内存,虚拟继承的内存布局,也会与单一继承和多重继承不一样:
虚拟继承的类层次关系结构有点复杂,有兴趣的朋友可以参考:What is the VTT for a class。
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include
class Base {
public:
virtual void vBaseFunc() {}
virtual void vBaseFunc2() {}
long long m_base_data = 0x11;
long long m_base_data2 = 0x12;
};
class Base2 : virtual public Base {
public:
virtual void vBaseFunc() {}
virtual void vBase2Func() {}
virtual void vBase2Func2() {}
long long m_base2_data = 0x21;
long long m_base2_data2 = 0x22;
};
class Base3 : virtual public Base {
public:
virtual void vBaseFunc2() {}
virtual void vBase3Func() {}
virtual void vBase3Func2() { std::cout << "Base3::vBase3Func2" << std::endl; }
long long m_base3_data = 0x31;
long long m_base3_data2 = 0x32;
};
class Derived : public Base2, public Base3 {
public:
virtual void vBase2Func() {}
virtual void vBase3Func2() { std::cout << "Derived::vBase3Func2" << std::endl; }
virtual void vDerivedFunc() {}
virtual void vDerivedFunc2() {}
long long m_derived_data = 0x41;
long long m_derived_data2 = 0x42;
};
Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 21u entries
0 64u
8 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
16 (int (*)(...))(& _ZTI7Derived)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Derived::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 (int (*)(...))Derived::vBase3Func2
56 (int (*)(...))Derived::vDerivedFunc
64 (int (*)(...))Derived::vDerivedFunc2
72 40u
80 (int (*)(...))-24
88 (int (*)(...))(& _ZTI7Derived)
96 (int (*)(...))Base3::vBaseFunc2
104 (int (*)(...))Base3::vBase3Func
# _ZThn24_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
112 (int (*)(...))Derived::_ZThn24_N7Derived11vBase3Func2Ev
120 18446744073709551576u # -40
128 18446744073709551552u # -64
136 (int (*)(...))-64
144 (int (*)(...))(& _ZTI7Derived)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
152 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
160 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev
Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0 64u
8 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16 (int (*)(...))(& _ZTI5Base2)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Base2::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 0u
56 18446744073709551552u # -64
64 (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72 (int (*)(...))(& _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88 (int (*)(...))Base::vBaseFunc2
Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0 40u
8 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16 (int (*)(...))(& _ZTI5Base3)
24 (int (*)(...))Base3::vBaseFunc2
32 (int (*)(...))Base3::vBase3Func
40 (int (*)(...))Base3::vBase3Func2
48 18446744073709551576u # -40
56 0u
64 (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72 (int (*)(...))(& _ZTI5Base3)
80 (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev
VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16 ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32 ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40 ((& Derived::_ZTV7Derived) + 152u)
48 ((& Derived::_ZTV7Derived) + 96u)
Class Derived
size=88 align=8
base size=64 base align=8
Derived (0x0x7fd19d7401c0) 0
vptridx=0u vptr=((& Derived::_ZTV7Derived) + 24u)
Base2 (0x0x7fd19d6aea90) 0
primary-for Derived (0x0x7fd19d7401c0)
subvttidx=8u
Base (0x0x7fd19d5ee840) 64 virtual
vptridx=40u vbaseoffset=-24 vptr=((& Derived::_ZTV7Derived) + 152u)
Base3 (0x0x7fd19d6aeaf8) 24
subvttidx=24u vptridx=48u vptr=((& Derived::_ZTV7Derived) + 96u)
Base (0x0x7fd19d5ee840) alternative-path
我们可以通过类的构造顺序去理解:对象内存布局如何一步一步构造出来的。在构造派生类 Derived 时,先构造基类,当基类构造完了,才构造自己。
|-- main
|-- ...
|-- Derived::Derived()
|-- Base::Base()
|-- Base2::Base2()
|-- Base3::Base3()
...
0x400b33: e8 34 02 00 00 callq 0x400d6c <Derived::Derived()>
...
0x400d83: e8 06 ff ff ff callq 400c8e <Base::Base()>
...
0x400d97: e8 20 ff ff ff callq 400cbc <Base2::Base2()>
...
0x400daf: e8 60 ff ff ff callq 400d14 <Base3::Base3()>
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))(& _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
Class Base
size=24 align=8
base size=24 base align=8
Base (0x0x7fd19d5ee720) 0
vptr=((& Base::_ZTV4Base) + 16u)
Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0 64u
8 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16 (int (*)(...))(& _ZTI5Base2)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Base2::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 0u
56 18446744073709551552u # -64
64 (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72 (int (*)(...))(& _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88 (int (*)(...))Base::vBaseFunc2
VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16 ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32 ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40 ((& Derived::_ZTV7Derived) + 152u)
48 ((& Derived::_ZTV7Derived) + 96u)
VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16 ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32 ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40 ((& Derived::_ZTV7Derived) + 152u)
48 ((& Derived::_ZTV7Derived) + 96u)
Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0 40u
8 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16 (int (*)(...))(& _ZTI5Base3)
24 (int (*)(...))Base3::vBaseFunc2
32 (int (*)(...))Base3::vBase3Func
40 (int (*)(...))Base3::vBase3Func2
48 18446744073709551576u # -40
56 0u
64 (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72 (int (*)(...))(& _ZTI5Base3)
80 (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev
int main() {
auto d = new Derived;
std::cout << d << std::endl;
auto b = static_cast<Base3 *>(d);
std::cout << b << std::endl;
b->vBase3Func2();
return 0;
}
// 输出:
// 0x9fa010
// 0x9fa028
// Derived::vBase3Func2
要理解多态的对象内存布局,要注意理解(多个)虚指针是如何根据不同的基类指针进行偏移的,当虚指针指向虚表后,要获得对应的虚函数,虚指针要偏移一定的位置才能定位到对应的虚表上的虚函数。
如果要用一个词来形容多态,那就是 覆盖
,派生类重写基类虚函数,像图层一样,(派生类)上层覆盖下层(基类),层层叠加,最后得出了被覆盖的结果;这也是我们理解 虚表
结构的核心思维方式。
关于有继承关系的 C++ 多态探索,因为本人水平有限,以上只作了一些基础简单的 Demo 的分析,还有一些应用场景没有涉及(例如 虚析构)。
很多技术细节没有在文章中提及,有兴趣的朋友可以动手写写 demo 用 gdb 调试一下,查看对象内存布局上的地址数据,以及反汇编查看对象构造的逻辑,是否与自己的理解一致,这样才能在不断变化的问题表象里,寻获答案本质。