C++对象模型
1.何为对象模型
C++对象模型可以概括为以下2部分:
① 语言中直接支持面向对象程序设计的部分
面向对象程序设计部分:如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等。
② 对于各种支持的底层实现机制
在C++类中有两种数据成员、三种成员函数:
两种数据成员:static、nonstatic。
三种成员函数:static、nonstatic、virtual。
- 成员函数不占用类对象的内存空间
- 一个类对象至少占用1个字节的内存空间
- 成员变量是占用对象的内存空间
- 成员函数 每个类只诞生 一个(跟着类走),而不管你用这个类产生了多少个该类的对象;
2 基本对象模型
简单对象模型和表格驱动对象模型这里不讲了,因为只是为了引出现在使用的c++对象模型
2.1 C++对象模型关键点
① nonstatic 数据成员被放置到对象内部;
② static数据成员、static and nonstatic 函数成员均被放到对象之外。
③ 对虚函数的支持分为两步:
a)每个class会为每个虚函数生成一个指针,这些指针统一放在虚函数表中(vtbl)
b)每个class的对象都会添加一个指针(vptr),指向相关的虚函数表(vtbl)。
vptr的设定(setting)和重置(resetting)都由每一个class的构造函数,析构函数和拷贝赋值运算符自动完成。
④ 另外,虚函数表地址的前面设置了一个指向type_info类的指针。
C++提供了一个type_info类来获取对象类型信息。
2.2C++对象模型优点与缺点
- 优点:在于它的空间和存取时间的效率
- 缺点:当所使用的类的non static数据成员添加删除或修改时,需要重新编译。
2.3 样例
class Base
{
public:
Base() = default;
virtual ~Base(void) = default;
virtual void Print()
{
cout << "this is print ..." << endl;
}
virtual void Test() const
{
cout << "this is test ..." << endl;
}
protected:
int iBase;
static int count;
};
int main()
{
Base base;
uintptr_t* vptr = (uintptr_t*)(&base); // 对象 base 的起始内存地址就是虚函数表地址
cout << vptr << endl;
uintptr_t* vfunc0 = (uintptr_t*)(*vptr); // 虚表地址里面取值是虚函数地址 析构函数
uintptr_t* vfunc1 = (uintptr_t*)(*vptr + 8); // Print()
typedef void(*func)(void);
((func)(*vfunc1))();
uintptr_t* vfunc2 = (uintptr_t*)(*vptr + 16); // Test()
((void(*)(void))(*vfunc2))();
return 0;
}
如上一个简单Base类的对象模型,其内存布局首地址是一个虚表指针指向虚函数表。接下来排布数据内存。在虚表之上还有一个type_info类来获取对象类型信息,这里不做介绍。
3 单层继承对象模型
3.1 三个子类的实现
子类Driver1、Driver2、Driver3都继承Base类,分别对继承的虚函数做部分是实现。
class Driver1 : public Base {
public:
virtual void Print()
{
cout << "this is Driver1 print ..." << endl;
}
};
class Driver2 : public Base {
virtual void Test() const
{
cout << "this is Driver2 test ..." << endl;
}
};
class Driver3 : public Base {
virtual void Print()
{
cout << "this is Driver3 print ..." << endl;
}
virtual void Test() const
{
cout << "this is Driver3 test ..." << endl;
}
};
3.2 内存布局及虚表
从上图可形象的看出子类虚表是在父类的虚表的基础上替换子类重写的虚函数。如果子类有自己特有的虚函数,则往后补充即可。比如:
4 多重继承
4.1 继承关系代码实例
class Base
{
public:
Base() = default;
virtual ~Base(void) = default;
virtual void Print()
{
cout << "this is print ..." << endl;
}
virtual void Test() const
{
cout << "this is test ..." << endl;
}
protected:
static int count;
int data;
};
class Base1
{
public:
Base1() = default;
virtual void Print1()
{
cout << "this is Base1 print ..." << endl;
}
virtual void Test1() const
{
cout << "this is Base1 test ..." << endl;
}
};
class Driver1 : public Base, public Base1 {
public:
virtual void Print()
{
cout << "this is Driver1 print ..." << endl;
}
virtual void Print3()
{
cout << "this is Driver1 print3 ..." << endl;
}
};
int main()
{
Driver1 base;
uintptr_t* vptr1 = (uintptr_t*)(&base); // 对象 base 的起始内存地址就是虚函数表地址
uintptr_t* vfunc0 = (uintptr_t*)(*vptr1); // 虚表地址里面取值是虚函数地址 析构函数
uintptr_t* vfunc1 = (uintptr_t*)(*vptr1 + 8); // Print()
typedef void(*func)(void);
((func)(*vfunc1))();
uintptr_t* vfunc2 = (uintptr_t*)(*vptr1 + 16); // Test()
((func)(*vfunc2))();
uintptr_t* vfunc3 = (uintptr_t*)(*vptr1 + 24); // Test()
((func)(*vfunc3))();
cout << sizeof(Base) << endl;
uintptr_t* vptr2 = (uintptr_t*)(&base) + 2; // 指向 base1虚表的指针,这里加2是因为base1 有一个数据
cout << (uintptr_t*)(&base) << endl;
cout << vptr2 << endl;
uintptr_t* vfunc2_1 = (uintptr_t*)(*vptr2);
((func)(*vfunc2_1))();
return 0;
}
4.2 内存布局和虚表关系
注意:child先继承base1的,所以child里面新的虚函数在这个虚表下。
5 菱形继承
5.1 类图
5.2 内存布局和虚表
注意:公共继承类在两个虚表中都有。
6 学习博客
[C++ 面试必问:深入理解虚函数表 - 知乎 (zhihu.com)
[详细介绍c++中的类对象内存模型-c++对象的内存模型 (51cto.com)
[C++对象模型_template_程序员之光的博客-CSDN博客](https://zhuanlan.zhihu.com/p/616522622?utm_id=0)
模型-c++对象的内存模型 (51cto.com)](https://www.51cto.com/article/270265.html)
[C++对象模型_template_程序员之光的博客-CSDN博客](https://zhuanlan.zhihu.com/p/616522622?utm_id=0)