C++对象模型读书笔记(一)

211 阅读4分钟

你真的了解 C++ 对象模型吗?(对自己提问)

在 C 语言中,“数据”和“函数”是分开申明的,而在 C++ 中,数据和函数是一同申明在一个 class 中,称为抽象数据类型(ADT)。

C++ 面向对象模型被提出了很多中,最终最采纳的是如下这种:

  • Nonstatic data members 放置在每一个 class object(类对象)内;
  • static data members 放置在 class object(类对象)外,具体应该存放在全局静态区;
  • static function members 和 non static members 都应该存放在代码段,不要因为 function 是 static 就误认为它存放于全局静态区;
  • 如果有 virtual function,那么每个 class 都会产生一堆指向虚函数的指针,放在虚函数表中(virtual table),每一个 class object 都会被安插一个 vptr ,指向 virtual table。vptr 的设定都是由构造函数、拷贝构造函数、赋值运算符自动完成的。每一个 class 所关联的 RTTI(runtime type identification)也是由 virtual tabel 指出的(我所知道的 RTTI 信息在使用 dynamic_cast 的时候会用到),且通常放置在 virtual table 的第一个 slot 中。 具体的图片参考《深入理解C++对象模型》P10

对于继承来说,也有一种类似的 base table 模型:

  • 每一个 class object 都拥有一个 bptr ,指向其 base class table;
  • 每一个 base class table 的每一个 slot 都内含一个相关的 base class 地址。 具体图片参考《深入理解C++对象模型》P11

对象模型如何影响程序? 这就要从静态类型和动态类型谈起了,如果一个类型是静态类型,那么在编译期就可以确定它的行为,而如果一个类型是动态类型,那么只有在运行期才能知道它的行为。动态绑定是通过 base class ptr 来实现的。

比如下面这个例子:

x foobar() {
    X xx;
    X* px = new X;
    
    // foo 是一个虚函数
    xx.foo();
    px->foo();
    
    delete px;
    return xx;
}

编译器会根据其类型处理,判断所访问静态类型的函数还是查虚函数表,对于 px ,就需要查寻函数表调用对应的函数。可以发现,C++对象模型影响了编译器对C++对象的解析。

C++ 对象模型支持三种程序范式:

  • 程序模型,和 C 语言一样,我想这是继承 C 库所表现出来的形式;
  • 抽象数据模型,由所谓的“抽象”和 public 接口提供,如为一个 class 实现 opeartor== ;
  • 面向对象模型,通过一个抽象基类(提供接口),其子类继承基类的成员以及接口(可以直接继承,也可以扩展)

C++ 如何支持多态?

首先需要明白一点,C++ 的多态只存在于 public class 的继承体系之中,为什么?因为其余继承方式,会导致基类 public 部分变得不可访问,这就违背了多态的初衷,多态是想让子类继承其接口,再扩展其接口。

  • 通过一组隐式的转换操作。如:将一个 derived class 指针转化成一个指向 public base type 的指针:
share* ps = new circle();
  • 使用虚函数;
  • 通过 dynamic_cast 和 typeid 来实现:
if (circle* pc = dynamic_cast<circle*>(ps) != nullptr)
...

老生常谈的问题:需要多少内存才能表示一个 class object ?

一个 class object 只包含 nonstatic members、vptr。其余的 member function存储在代码段,static data members 存放在静态存储区。所以一个 class object 占用多少内存取决于 nonstatic member 的数量以及类型,再加上一个 vptr 大小,最后还得考虑内存对齐(提高 CPU 的处理效率)。 具体图片参阅《C++对象模型》P28

最后我还想了解一下为什么可以通过指针(or 引用)就能在运行时找到正确的类型?

其实在内存结构上看,指针并无区别,都是指向地址,且指针本身的大小都是 4 Byte(32 bit),那为什么说多态可以依赖 base class type ptr 来实现呢?这就涉及到了编译器如何解释某个特定地址的内容和大小,一切都取决于编译器!

以上就是我读了《深入理解C++对象模型》的感悟。