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

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

s

  • 如果有以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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的。
  • 其顺序可以从代码层面这样的解释
1
2
3
4
5
6
7
8
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)是虚指针指向的虚表,虚表中其实是函数地址的数组,调用某个函数就是去数组的值,然后传递参数。

1
( *(p->vptr) [n] )(...)

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

1
2
3
4
5
B b;

A a = (A)b;

a.vFunc();
# CPP
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×