很久以前,就购买了《深度探索C++对象模型》这本书,刚入手时,翻看了很久,以为受益不浅。最近发现,这本书中的知识,在脑海中,已经变得很不清晰了,本着好记性不如烂笔头的传统,打算用一段时间,把其中比较重要,尤为是在实际工做中,真的会使用到的知识,作一番整理。今天开始第一篇,系统介绍下C++ 对象模型。ide
在简单对象模型中,一个对象中,包含了一系列的slots。这些slot均为一个指针,所指向的地址,对应的为类中定义的对象或者方法。函数
好比定义一个类布局
class foo { public: foo(); ~foo(); int number; int add(); };
那么在定义一个对象obj
以后,obj
的结构以下:设计
| slot | meaning|
| --- | --- |
| 0 | pointer to foo() |
| 1 | pointer to ~foo() |
| 2 | pointer to number |
| 3 | pointer to add(int num) |指针
在这种模型下,可以避免由于成员类型的不一样,而形成额外存储空间的不一样,也就是说,全部对象的大小,均为 成员数量 x sizeof(pointer)
。code
注意,这种简单的对象模型,这是一个设想,并无真的在C++语言中被采用。不过这种指向成员指针的思想却在实际C++对象模型中得以继承。
与简单对象模型不一样,表格驱动模型中,区分了成员变量,与成员函数。在一个对象中,会包含两个指针,一个指针指向存储了全部成员变量的表格,一个指针指向存储了全部成员函数指针的表格。对象
好比定义一个类:继承
class foo { public: foo(); ~foo(); int number; int count; int add(); int subtract(); int multiplied(); int divided(); };
那么在定义个obj
以后,具体的存储方式以下ip
pointer | table | table member |
---|---|---|
data pointer | data table | number |
count | ||
function pointer | function table | pointer to add |
pointer to multiplied | ||
pointer to divided |
注意,这种表格驱动对象模型也没有被C++语言采用,不过
member function table
的观念,被后续的virtual functions的实现所采纳。
由Stroustrup最初设计的C++对象模型是从简单模型演变而来的,其中:内存
非静态成员变量(nonstatic data members)
被放置到对象中。静态成员变量(static data members)
被放置在全部对象以外,单独进行存储。非virtual成员函数,包括静态(static)与非静态(nonstatic)
被放置在全部对象以外。vptr
的指针,该指针指向一个名为virtual table
的表格,该表格存储了类中定义的全部虚函数(virtual function)
好比定义一个类:
class foo { public: foo(); ~foo(); int number; static int count; int add(); static int subtract(); virtual int multiplied(); virtual int divided(); };
那么定义一个obj
以后,具体的存储方式以下:
location | member | table member |
---|---|---|
obj | number | --- |
vptr | pointer to multiplied() | |
pointer to diviede() | ||
out of obj | count | |
add() | ||
substract() |
在单一继承的C++对象模型中,派生类对象中,会包含全部父类中的非静态(nonstatic)成员变量
,可以访问类中定义的全部静态(static)变量
,静态(static)与非静态(nonstatic)的成员函数
,对于虚函数(virtual function)
,若是派生类中对虚函数作重写(overwrite)
,则会覆盖父类中虚表(virtual table)
中的函数指针。
class Point2D { public: int x; int y; static int count; int get_x(); int get_y(); virtual int foo(); static int get_count(); }; class Point3D_0 : public Point2D { public: int z; int get_z(); }; class Point3D_1 : public Point2D { public: int z; int get_z(); virtual int foo(); };
Point2D obj的内存布局以下:
location | member | table member |
---|---|---|
obj | x | --- |
y | --- | |
vptr | pointer to Point2D::foo() | |
out of obj | Point2D::count | |
Point2D::get_x() | ||
Point2D::get_y() | ||
Point2D::get_count() |
Point3D_0 obj的内存布局以下:
location | member | table member | table member |
---|---|---|---|
obj | Point2D | Point2D::vptr | pointer to Point2D::foo() |
Point2D::x | --- | ||
Point2D::y | --- | ||
z | --- | ||
out of obj | Point2D::count | ||
Point2D::get_x() | |||
Point2D::get_y() | |||
Point2D::get_count() |
Point3D_1 obj的内存布局以下:
location | member | table member | table member |
---|---|---|---|
obj | Point2D | Point2D::vptr | pointer to Point3D::foo() |
Point2D::x | --- | ||
Point2D::y | --- | ||
z | --- | ||
out of obj | Point2D::count | ||
Point2D::get_x() | |||
Point2D::get_y() | |||
Point2D::get_count() |
在多继承(非菱形继承)中,派生类对象会包含全部基类中的非静态(nonstatic)成员变量
,可以访问类中定义的全部静态(static)变量
,静态(static)与非静态(nonstatic)的成员函数
。可是这里须要注意的是全部基类中的静态(static)与非静态(nonstatic)变量或者函数方法的名字,不可以出现冲突
,不然会形成派生类对象调用时的不明确,编译器会直接报错。
对于虚函数(virtual function)
,派生类若是没有对父类中的虚函数(virtual function)
作重写(overwrite),那么全部父类中的虚函数不能冲突,不然因为调用不明确出现编译错误。若是派生类对父类中的虚函数(virtual function)
作了重写(overwrite),那么子类的虚函数被放在声明的第一个基类的虚函数表中。
class base1 { private: int a1{ 1 }; public: virtual void print() { std::cout << "base 1" << std::endl; } int get_1() { return a1; }; }; class base2 { private: int a2{ 2 }; public: virtual void print() { std::cout << "base 2" << std::endl; } int get_2() { return a2; }; }; class child : public base1, public base2 { private: int c{ 0 }; public: virtual void print() { std::cout << "child" << std::endl; } };
child obj 内存布局
location | member | member | member |
---|---|---|---|
obj | base1 | base1::vptr | pointer to chlid::print() |
base1::a1 | --- | ||
base2 | base1::vptr | pointer to base2::print() | |
base2::a2 | --- | ||
c | --- | --- | |
out of obj | base1::get_1() | --- | --- |
base2::get_2() | --- | --- |
菱形继承指的是基类被某个派生类简单重复继承了屡次,从而会在派生类中出现多个基类实例。这种状况下,派生类对象去调用基类中成员变量时,就会出现调用的不明确,从而致使程序的行为不可测。为了解决这种问题,C++对象模型中引入了虚继承的概念。
在虚继承中,派生类会生成一个隐藏的虚基类指针(vbptr)
用于存放最开始的父类。结构以下:
class base { public: int a{ 0 }; int b{ 1 }; }; class base1 : public virtual base { public: base1() { a = 1; } virtual void print() { std::cout << "base 1" << std::endl; } }; class base2 : public virtual base { public: base2() { a = 2; } virtual void print() { std::cout << "base 2" << std::endl; } }; class child : public base1, public base2 { private: int c { 10 }; public: virtual void print() { std::cout << "child: " << a << std::endl; } };
child obj内存布局
location | member | member | member |
---|---|---|---|
obj | base1 | vbptr, point to child::base | --- |
base1::vptr | pointer to child::print() | ||
base2 | vbptr, point to child::base | --- | |
base2::vptr | pointer to base2::print() | ||
base | a | --- | |
b | --- | ||
c | --- | --- |