第四章,效率

24-了解虚函数继承与多态相关机制的成本

虚函数表vtbl

凡声明虚函数的class,就会产生虚函数表vtbl,其中的条目就是各个虚函数实现体的指针,实例化对象后会生成一个指向虚函数表的虚表指针vptr

MSVC在每个包含类定义的翻译单元都生成vtable,链接时去重;GCC/Clang以关键函数(第一个非纯虚、非inline虚函数)所在目标文件为锚点生成vtable,几乎无需去重。MSVC的策略增加了链接期成本

虚表指针vptr

凡声明虚函数的class,其对象都含有一个隐藏的data member————虚表指针vptr,被编译器加入对象内某个特定的位置(由编译器决定)

pC1->f1();
//产生出来的代码将是:
(*pC1->vptr[i])(pC1);//调用虚函数f1:通过虚表指针获取虚函数表中的f1函数指针

虚函数不能被申明为inline,因为inline是在编译时期将实现代码替换到调用端,但是虚函数的调用是运行时期通过虚表指针调用的。这意味着无法将虚函数inlined,这也是一笔成本。(代价)

多继承产生的成本

class A { ... };
class B: virtual public A{ ... };
class C: virtual public A{ ... };
class D: public B, public C{ ... };

//D多继承自B,C; 其对象内存中会包含B和C的虚表指针,基类A的虚表指针,而其本身会和B共享同一个虚表指针(大部分编译器如此,减少开销)

RTTI

RTTI(runtime type identification)运行时期获得objects和classes的相关信息,通过在虚函数表中事前存储类型信息来实现。(可通过typeid操作符获取一个class对于的type_info对象)

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

virtual void speak() {};
};

class Dog : public Animal
{
public:
Dog() {};
~Dog() {};
};

int main(int argc, char *argv[])
{
Animal* aniaml = new Dog();
Dog* d = dynamic_cast<Dog*>(aniaml);
}

//如果注释掉virtual void speak() {};
//dynamic_cast将报错:必须包含多态类类型
//因为不存在虚函数表,无法将类型信息存储以供运行时识别