《深度探索C++对象模型》第七章重点梳理。主要内容包括:
- 模板(Template)
- 执行期类型识别(Runtime Type Identification,RTTI)
1 模板(Template)
1.1 模板的实例化
一个模板只有被使用到,才会被实例化,否则不会被实例化。对于一个实例化后的模板来说,未被调用的成员函数将不会被实例化,只有成员函数被使用时,C++ 标准才要求实例化他们。其原因,有两点:
- 空间和时间效率的考虑,如果模板类中有 100 个成员函数,对某个特定类型只有 2 个函数会被使用,针对另一个特定类型只会使用 3 个,那么如果将剩余的 195 个函数实例化将浪费大量的时间和空间。
- 使模板有最大的适用性。并不是实例化出来的每个类型都支持所有模板的全部成员函数所需要的运算符。如果只实例化那些真正被使用的成员函数的话,那么原本在编译期有错误的类型也能够得到支持。
1.2 模板的名称决议
对于一个模板类来说,需要明确两个范围:一个是定义模板的范围(scope of the template definition),另一个是实例化模板的范围(scope of the template instantiation)。
下面来举例说明:
1 | // scope of the template definition |
对于上面的语句(1),将会调用哪一个 foo()
函数呢?答案是 double foo ( double )
,这是一个完全反直觉的答案,原因在于 invariant()
函数调用了一个外部函数 foo()
,但传入的参数是模板类中固定类型的成员 _val
,无论模板被实例化为什么类型,其中的 _val
都是 int 型,也就是说它完全与实例化模板的参数的类型无关,此时对于外部函数名称的决议会在定义模板的范围内进行,在上面的例子中,定义模板的范围内只有一个 foo()
函数的声明,所以即使 _val
是 int 型,也会调用 double 作为参数的 foo()
函数。
而对于语句(2),会调用 int foo( int )
,因为 type_dependent()
函数将与实例化模板的参数类型有关的成员作为参数传给了外部函数,此时就无法在定义模板类的范围内决议使用哪个 foo()
函数,所以要推迟到模板被实例化之后再决议,因此会在实例化模板的范围内寻找合适的函数,上面的例子中,实例化模板的范围中有两个声明的 foo()
函数,所以会选择和模板类型匹配的 foo(int)
函数进行调用。
总结一下,在模板中,一个非成员名称的决议在于它适不适合在当前决议,当它完全与实例化模板的参数类型无关的时候,就可以在当前决议下来;如果有关的话,则认为不适合在当前决议下来,于是将被推迟到实例化这个模板的时候才决议。
2 执行期类型识别(Runtime Type Identification)
在一开始学习多态的时候就已经了解过 RTTI,这里最后进行总结:
- RTTI 只支持多态类,也就是说没有定义虚函数是的类是不能进行 RTTI 的,这是因为 RTTI 的实现是通过 vptr 来获取存储在虚函数表中的
type_info*
,事实上为非多态类提供 RTTI 也没有多大意义 - 对指针进行
dynamic_cast
失败会返回 NULL,而对引用的话,失败会抛出bad_cast exception
,这是由于指针可以被赋值为 0,以表示 no object,但是引用不行 typeid
可以返回const type_info&
,用以获取类型信息。虽然第一点指出 RTTI 只支持多态类,但typeid
和type_info
同样可用于内建类型及所有非多态类。与多态类的差别在于,非多态类的type_info
对象是静态取得(所以不能叫“执行期类型识别”),而多态类的是在执行期获得。