CPP

侯捷C++学习笔记:Object Model

Posted by 蔡华的博客 on January 9, 2018

composition & delegate & inherit 的构造和析构顺序

  • 如果有以下代码:
class A
{
public:
    A();
    ~A();
    Foo();
}

class B
{
public:
    B();
    ~B();
}

class C:A
{
public:
    C();
    ~C();
private:
    B* b;
}

main()
{
    C c;
}

  • 在前面面向对象中讲过,B和C组成了composition & delegate,而A和C是继承关系。
  • 那么在内存使用的顺序上会先调用A的构造函数,然后调用B的构造函数。最后调用C的构造函数。
  • 在析构时的顺序是:先调用C的析构,在调用B的,最后调用A的。
  • 其顺序可以从代码层面这样的解释
C::C() :A(), B() { // c在构造函数中要做的事情}

C::~C()
{
    // c的析构
    ~B();
    ~C();
}

vptr & vtbl

  • 在C++中函数分为虚函数和非虚函数,对于非虚函数来说调用的时候编译器会编译为call(method address)的形式,也就是说函数的地址是已知的,即使是继承下来的函数,等于也是调用了父类的函数的地址。这个被称为静态绑定。
  • 但是对于虚函数,因为有override的情况,所以C++中采用了虚机制,也可以说是动态绑定。
  • 虚机制的基础是虚指针和虚表。虚指针是对象中的一个指向虚表的指针,虚表是一个对象中所有的虚函数地址表。
  • 与C#方法列表进行比对: C#中方法列表包含了所有函数的地址,而C++的虚表中只是虚函数的。因为不知道子对象是否会override父类虚函数,所以这个虚表里面的函数可能指向了父类的虚函数,也可能指向了子类的虚函数,还有可能是子类override的虚函数。
  • 什么时候C++会使用动态绑定呢?
    • 调用方法的对象是一个指针
    • 对象使用时是up cast,也就是协变。
    • 调用的是虚函数
  • 本质:从下面的代码看*(p->vptr)是虚指针指向的虚表,虚表中其实是函数地址的数组,调用某个函数就是去数组的值,然后传递参数。
( *(p->vptr) [n] )(...)

PS:对于栈上的对象的方法调用,就不考虑虚机制。下面例子中B继承自A,AB中均有虚函数vFunc的情况下,结果是调用了A的虚函数,因为a不是指针。如果a是指针,那么走虚机制,应该执行的是B的vFunc。

B b;

A a = (A)b;

a.vFunc();