composition & delegate & inherit 的构造和析构顺序
s
- 如果有以下代码:
1 | class A |
- 在前面面向对象中讲过,B和C组成了composition & delegate,而A和C是继承关系。
- 那么在内存使用的顺序上会先调用A的构造函数,然后调用B的构造函数。最后调用C的构造函数。
- 在析构时的顺序是:先调用C的析构,在调用B的,最后调用A的。
- 其顺序可以从代码层面这样的解释
1 | C::C() :A(), 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 | B b; |