深度探索C++对象模型

  1. 第一章 关于对象
  2. 第三章Data语义学
  3. 第四章
  4. 第五章
  5. 第六章
  6. 第七章

第一章 关于对象

加上封装之后的成本增加了吗?

并没有增加成本, C++在布局以及存取时间上主要的额外负担是由virtual引起的

  • virtual func机制 用来支持一个有效率的执行期绑定
  • virtual base class 用来实现多次出现在继承体系中的base class, 有一个单一而被共享的实例

C++ 有两种成员变量staticnonstatic
三种成员函数static, nonstaticvirtual

第三章Data语义学


class Foo
{
};

Foo foo;

sizeof foo = ?答案是1, 这是为了防止不同的对象却有相同的地址

同时class也存在字节对齐现象

在针对虚拟继承问题的时候

D对象的内存结构  从低地址开始

D对象数据部分  《- D类对象指针 dptr 指向这里 可以从vptr指向的表中获取A对象数据部分的偏移量 offset
C对象数据部分
B对象数据部分
A对象数据部分  《- dptr + offset 则实现了 子类指针向父类指针的转化

A 派生出 B C。 B C派生出D 如何保证在BC对象中都有A对象, 而在D对象中只有一个A对象?

一种方法是

先安排派生类的部分 再后接基类的部分, 这样派生类D的对象指针就是指向自己数据部分的指针

如果想要实现子类向父类转换呢?

在vtpr指向的表中的 -1 位置放置偏移量, 由于D类对象的指针是内存开始位置 如果要转换成A类指针

则从D类对象的vptr指向的表中获取A类数据部分的偏移量 这样就实现了子类向父类转换

class Foo
{
public:
    int x;
    int y;
    int z;
};

int main()
{
    printf("&Foo::x = %p\n", &Foo::x);  // 0x0
    printf("&Foo::y = %p\n", &Foo::y); // 0x4
    printf("&Foo::z = %p\n", &Foo::z); // 0x8
    return 0;
}

如果x, y, z定义为protected或者private则会报错。public得到的是其在类内的偏移地址

第四章

name-mangling

不要将mangling之后的结果显示给用户看,这样用户会疑惑哪里来的这个函数(大体来说 我们采纳了他的建议)

static member func 并不是比 nonstatic member func效率高 后者也会转换成类似前者的调用。

static member func的主要特征就是他没有this指针

  • 所以它不能存取nonstatic member
  • 不能被声明为const 因为const成员函数最终转化成了 const修饰的this指针
  • 不需要通过对象来调用, 因为他不需要this指针

C++中多态表示以一个public base class的指针(或reference)寻址出一个derived class object。这样就实现了多态的函数调用机制

三目运算符搭配逗号运算符

第五章

vptr的初始化发生在 base class constructor调用之后,程序员提供的代码之前

不要再vurtual base class中声明数据

vptr会随着构造函数从基类到子类的一层层调用被改变
同样 析构函数会逆操作 这个指针

第六章

一般情况下 程序的构造函数我们都知道他在那里调用。 然而,析构函数呢?答案是析构函数会在函数结束的时候被调用(针对局部对象)。所以析构函数的调用代码会被穿插入每一个return 前(对象被构造后的return 被构造前的则没有必要) 尽管某些return不会被调用

new运算符只有一个步骤? 不不不 会被扩充为 1. 先分配内存 2. 设定初值

delete运算符 则会进行检测 delete nullptr 则不会有任何效果

针对对象使用new则会在分配内存后通过调用构造函数设定初始值, 而delete则会在检测nullptr后调用析构函数然后将内存归还

NRV优化

第七章

template, exception handling(EH), runtime type identification(RTTI, EH的副作用?)

模板类的错误发现 会直到被实例化的时候才会被发现 延迟了错误的发现时机

目前的编译器,面对一个template声明,在它被一组实际参数实例化之前,只能施行有限的错误检查。template中那些与语法无关的错误,程序员可能认为十分明显,编译器却让他通过了,只有在特定实例被定义之后,运行时。才会产生错误。

dynamic_cast 运算符可以在执行期间决定真正的类型。 如果downcast是安全的这个运算符会传回适当转换过的指针。如果是不安全的则返回0
dynamic_cast同样可以对引用进行操作,成功后返回转换后的引用,失败后只能抛出异常(因为没有办法像指针那样指定nullptr表示错误)