第1章 关于对象
C++对象的布局成本
- C++中单纯地使用封装和继承,并不会增加存取时间和存储空间上的成本。
- C++真正需要的额外成本是由
Virtual
引起的(包括Virtual function
和Virtual base class
)。
C++对象模型
- 数据成员:
Nonstatic data members
被配置于每一个class object
之内;Static data members
被存放在个别的class object
之外。 - 非虚函数成员:
Nostatic/Static function members
均被置于个别的class object
之外,同一般的非成员函数处理机制类似。 - 虚函数成员:
(1)每一个class
产生出一堆指向virtual functions
的指针,存放在一个叫做virtual table(vtbl)
的表格之中。通常一个class
所关联的type_info object
被存放在该表格的第一个slot
中。
(2)每一个class object
被安插一个叫做virtual pointer(vptr)
的指针,指向相关的virtual table
。通常vptr
会被每一个class
的constructor
,destructor
和copy assignment
等函数自动地进行设定和重置。
第2章 构造函数语意学
Default Constructor的构造操作
对于一个类,如果没有任何user-declared constructor
,那么在编译器有需要的时候,会为它合成一个nontrival default constructor
。通常,有如下4种情况需要合成copy constructor
:
- 该
class
有”带Default Constructor
“的Data member
:
需要在构造函数中调用该Data member
的默认构造函数初始化该成员。 - 该
class
有”带Default Constructor
“的Base class
:
需要在构造函数中调用Base class
的默认构造函数初始化Base class subobject
。 - 该
class
有”Virtual Function
“:
需要在构造函数中生成或维护vtbl
,并设置或修改vptr
。 - 该
class
有”Virtual base class
“:
需要在构造函数中维护vtbl
和vptr
,保证virtual base class
在每一个derived class object
中的位置在执行期准备妥当。
至于没有存在上述4种情况且没有声明任何constructor
的class
,我们说它拥有的是implicit trivial default constructor
,它们实际上不会被合成出来。
C++新手一般有两个常见的误解:
(1)任何class
,如果没有定义default constructor
,就会被合成出来一个。
(2)编译器默认合成出来的default constructor
会显式地设定“class
内每一个data member
的默认值”。
Copy Constructor的构造操作
调用Copy constructor
三种情况分别是:初始化,传参和返回值。和default construtor
一样,copy constructor
也分为trivial
和nontrivial
两种,只有nontrivial
的class
才会合成它。倘若一个class
展现出所谓的”bitwise copy semantics
“,那么它的copy constructor
就是trivival
的。一个class
不展现出”bitwise copy semantics
“的4种情况如下:
- 当
class
内含有一个member object
,而后者的class
声明有一个copy constructor
时(不论是被class
设计者显式声明,还是被编译器合成都算),该class
的copy constructor
需要调用该member object
的copy constructor
完成该成员的拷贝构造。 - 当
class
继承自一个base class
,而后者存在一个显式声明的copy constructor
时,该class
的copy constructor
需要调用base class
的copy constructor
完成base class subobject
的拷贝构造。 - 当
class
声明了一个或多个virtual functions
时,该class
的copy constructor
需要维护vptr
指针,保证其指向对应的class
的vtbl
。 - 当
class
派生于一个继承串链,其中有一个或多个virtual base classes
时,该class
的copy constructor
需要拷贝构造virtual base subobject
,同时维护vptr
指针确保其指向对应的class
的vtbl
。
程序转化语意学
- 参数初始化:在非引用参数传入之前,可能会先创建一个临时变量存储参数。然后在调用函数时以引用方式传入这个临时变量,当然函数的声明也会做相应的修改。
- 返回值初始化:如果函数的返回值不是引用变量,会在函数调用前创建一个临时变量存储返回值。然后在调用函数时以引用方式传入这个临时变量,当然函数的声明也会作相应的修改。
- 使用者层面的优化:如果函数的返回值不是引用变量,则在函数
return
时创建临时变量比在函数开始处创建临时变量要好,有可能会减少一次copy constructor
的调用。如果不懂,可以联系上一条结论理解。 - 编译器层面的优化:如果函数中所有
return
指令传回相同的变量,编译器有可能会以返回值result
参数取代named return value
, 这个过程也被叫做NRV优化。 Copy constructor
的取舍:当class
的default copy constructor
被时为trivial
的时候,一般情况下不需要为其提供emplicit
的copy constructor
。然而如果class
需要大量的memberwise
的初始化操作,那么就可以给它提供一个copy constructor
;一方面结合编译器作NRV优化,另一方面可以用memcpy
函数提高拷贝效率。
成员初始化
Member initialization list
必需的情况:初始化一个reference member
;初始化一个const member
;初始化member object
时需要调用含参构造函数;初始化base class
时需要调用含参构造函数。Member initialization list
的初始化顺序:一个object
的构造总是先构造其base subobject
部分,而member
的初始化总是要按顺序被安插在任何explicit user code
之前。在每一个constrctor
中的member
初始化时,需要先调用base class
的constructor
,然后在按成员在class
中的顺序逐一初始化各个成员,最后执行explicit user code
。- 初始化的一些特殊情况:以
member function
的结果初始化member
;以member function
的结果作为base class constructor
的参数。Member function
的调用是合法的,因为在object
相关的this
指针在构建时已经准备妥当,但还是要尽量避免上述情况。具体原因时,构造函数调用前,object
的data member
有可能还未被全部初始化;而在调用构造base class subobject
时,通过this
指针调用虚函数时将有可能发生异常的动态绑定过程。
第3章 Data语意学
Data member的绑定
编译器对于一个inline member function
的本体的分析,会在整个class
的声明都出现了才开始;然而对于member function
的argument list
,则是在它们第一次出现时被适当地进行决议完成的。因此上,在现代C++程序设计中,需要将”nested type
声明”放在class
的起始处,这是一种安全的防御性程序设计风格。
Data member的布局
C++ Standard
要求,在同一个access session
中,较晚出现的members
在class object
中有较高的地址。同时,编译器还可能会合成一些内部使用的data member
(如vptr),传统上它们通常被放在所有显式声明的
members
之前或者之后。
Data member的存取
-
Static data members
对每个class
来说只有一个实例,存放在从class
之外的data segment
中,并被视为一个global
变量;不论通过实例访问,还是通过类名访问,都会在内部被转化成类名的访问形式。 -
Nonstatic data members
直接存放在每一个object
当中。每一个nonstatic data member
的偏移位置在编译时期即可获知,因此存取一个nonstatic data member
的效率和存取一个C struct member
的效率是一样的。唯一的例外是,当使用指针或引用访问member
时,该指针或引用指向的class
的继承结构中有一个virtual base class
,且该member
是从该virtual base class
继承而来的。这种情况下,因为不知道该指针或引用指向哪一种class type
,因此存取操作必须延迟至执行期,经由一个额外的间接引导才能解决。
继承与data member
- 单一继承不含
virtual functions
:- 一个
object
由两部分构成,base class subobject
和本身的data members
。这种情况下并不会增加空间或者时间上的额外负担。
- 一个
- 单一继承且含
virtual functions
:- 为每一个
class
导入一个virtual table
,用来存放它所声明的每一个virtual function
的地址。 - 在每一个
class object
种导入一个vptr
,提供执行期的链接,使每一个object
都能找到相应的virtual table
。对base class
来说,vptr
在object
中的位置通常在所有data members
之后;而derived class object
会继承base class
的vptr
,不用单独再留存储空间。 - 加强
constructor
,使它能够为vptr
设定初值,让它指向class
所对应的virtual table
。这可能意味者在derived class
和每一个base class
的constructor
中,会重新设定vptr
的值。 - 加强
destructor
,使它能够矫正vptr
,让它指向class
所对应的virtual table
。因为,vptr
很可能已经在derived class destructor
中被设定为derived class
的virtual table
的地址了。
- 为每一个
- 多重继承:
- 对于多重继承来说,一个
derived class object
由几部分组成:base class1 subobject,base class2 subobject, ..., data members
。在这种情况下,把一个derived object
转换为其base
类型,就需要编译器的介入,用以调整地址。 - 多重继承的情况下,如果一个
derived class object
有vptr
,那么其vptr
就存放在其继承列表中第一个含有vptr
的subobject
的vptr
处,否则在data members
之后新增一个vptr
域。
- 对于多重继承来说,一个
- 虚拟继承:
- 对于虚拟继承来说,它跟多重继承的情况类似。只是它需要指出
shared base subobject
的偏移位置,而这个偏移量通常会被放置在virtual table
最前面的slot
中。在将derived object
赋值给base object
时,需要通过virtual table
查找这个偏移量,并进行复制构造。 - 当通过
object
来存取一个从virtual base class
的member
,可以被优化成一个直接存取操作;然而通过指针或引用来访问上述member
时,就需要在执行期通过查找virtual table
中的offset
,经过两次间接引导才能访问到。因此上,virtual base class
最有效的一种运用形式就是:一个抽象的virtual base class
,没有任何的data member
。
- 对于虚拟继承来说,它跟多重继承的情况类似。只是它需要指出
指向data members的指针
- 对
class
的nonstatic member
取地址得到的是一个偏移值,表示该member
在class
中的偏移位置,可以将该值赋给一个指向该class
的data member
的指针;而对object
的nonstatic member
取地址得到的是一个地址值,表示该object
中的data member
的地址,可以将该值赋给一个指向该member
类型的指针。 - 对
class
或者object
中的static member
取地址得到的是一个地址值,表示该class
中static member
的在内存中的位置,可以将该值赋给一个指向该member
类型的指针。 - 为了区分一个”没有指向任何
data member
的指针”和”一个指向第一个data member
的指针”,每一个真正的member offset
值都会被加上1,而在使用该offset
取值之前,需要减掉1。
第4章 Function语意学
Member function的各种调用方式
Nonstatic member functions:
- C++的设计准则之一就是:
nonstatic member function
至少必须和一般的nonmember function
有相同的效率。 - 在编译器内部,会将
member function
实例转换为对等的nonmember function
实例。通常member function
的函数名称会被”mangling
“为程序中独一无二的名称,其函数原型会被安插一个额外的this
指针参数,其”对nonstatic data member
的存取操作”会被改写成通过this
指针的存取操作。
- C++的设计准则之一就是:
Virtual member functions
:- 如果通过指针或引用去访问一个
virtual member function
,将在编译器内部被转换为通过查询virtual table
进行访问的形式。 - 如果通过
object
取访问一个virtual member function
,其在编译器内部的转换方式跟nonstatic member function
相同。
- 如果通过指针或引用去访问一个
Static member functions
:- 无论通过何种形式访问一个
static member function
,总是会被转化一个对对等的nonmember function
的访问形式。通常static member function
的函数名称会被”mangling
“为程序中独一无二的名字。 Static member functions
的主要特性就是它没有this
指针。因此上,它不可以访问class
中的nonstatic members
,也不能够被声明为const
,volatile
或virtual
。
- 无论通过何种形式访问一个
Virtual member functions
- 单一继承下的
virtual functions
:- 含有
virtual functions
的单一继承下,一个class
只会有一个virtual table
,每一个table
中含有该从class
的类型信息和对应class object
中所有active virtual functions
函数实例的地址。其中的active virtual functions
包括继承自base class
的virtual functions
,重写的base class
的virtual functions
,自己定义的virtual functions
。 - 含有
virtual functions
的单一继承下,一个class object
中会被安插一个指向该class virtual table
的指针。为了找到函数地址,编译器会为每一个virtual function
指派一个表格索引值。
- 含有
- 多重继承下的
virtual functions
:- 含有
virtual functions
的多重继承下,一个derived class
内含有n个virtual tables
,n表示其上一层的base class
的个数。其中包含1个主要实例(由derived class
于base1 class
共享),n-1个次要实例(除base1 class
之外的每个base class
各享一个).针对每一个virtual table
,derived
对象中都有一个对应的vptr
,需要在构造,复制,析构时进行适当的维护。为了调节执行期链接器的效率,编译器或许会将多个virtual tables
连锁为一个,指向次要表格的指针,可以主要表格名称加上一个offset
获得。 - 在多重继承中支持
virtual functions
,其复杂度围绕在第二个及后继的base class
身上,以及必须在执行期调整this
指针这一点上。具体地有三种情况,第二或后继的base class
会影响对virtual function
的支持:(1) 通过一个指向”第二个base class
或后继”的指针,调用derived class virtual function
;(2) 通过一个指向”derived class
“的指针,调用第二个base class
或后继中继承而来的virtual function
;(3)允许一个virtual function
的返回值类型有所变化,可能时base type
,也可能时derived type
。
- 含有
- 虚拟继承下的
virtual functions
:- 含有
virtual functions
的虚拟继承下,编译器会在生成virtual table
的时,在table
的首部加入一个额外的slot
(索引为-1),指出virtual base
的偏移量。 - 强烈建议,不要在一个
virtual base class
中声明nonstatic data members
,只将它作为一个接口使用即可。
- 含有
指向member function的指针
Nonvirtual member function
指针:对一个nonstatic member function
取地址得到的是一个带有相同参数+额外参数(this
指针)的普通函数指针;而对一个static member function
取地址得到的是一个相同参数的普通函数指针。Virtual member function
指针:- 对一个单一继承下的
virtual member function
取地址得到的它在virtual table
中的索引值;对一个多重继承下的virtual member function
取地址得到的是一个结构体,除了反应virtual table
索引值之外,还要反应this
指针的offset
值。
- 对一个单一继承下的
Inline functions
- 定义在类内的函数,隐式为内联的;在类外声明的内联函数,须加
inline
声明。并非所有的inline
声明都会变成inline
函数,代码过多的话,编译器可能会拒绝,转而将它作为普通函数。 - 内联函数的形参:传入参数,直接替换为参数名;传入常量,直接替换为常量值;传入函数运行结果,则需要导入临时变量。
- 内联函数的局部变量:局部变量会被”
mangling
“,以便inline
函数被替换后局部变量名字唯一。也就是说,一次性调用N次,就会出现N个临时变量,程序体积会暴增。总之,inline
的使用要小心处理。
第5章 构造,析构,拷贝语意学
关于纯虚函数
原则上允许定义和调用一个pure virtual function
。不过它只能被静态调用(即通过类名调用),不能经由虚拟机制调用(即通过指针或引用调用).不过pure virtual destructor
必须被定义,因为每一个derived class destructor
都会被编译器加以扩张,以静态方式调用其”每一个virtual base class
“和”上一层base class
“的destructor
。所以一个比较好的替代方案是,不要把virtual destructor
声明为pure
。
无继承情况下的对象构造
Plain OI Data
声明形式:在这种情况下,该类的constructor
和destructor
被视为trival
的,不是没被定义就是没被调用。此时的copy assignment operator
会直接进行像C那样的纯粹位搬移操作。Abstract Data Type
声明形式:在这种情况下,该类的表现与上一种情况类似。简单的封装并没有给C++带来任何额外的负担。- 含有
virtual function
的情况:在这种情况下,destructor
仍然是trival
的,而constrcutor
和copy operator
不再是trival
的了。因为virtual functions
的出现,使得每一个object
含有一个vptr
,所以constructor
,copy constructor
和copy operator
都需要被编译器安插代码初始化object
的vptr
指针。
继承体系下的对象构造
继承体系下constructor
的扩充步骤:
- 如果有,调用所有的
virtual base class constructors
,从左到右,从深到浅。- 如果
class
被列于member initialization list
中,那么任何显式指定的参数都应该被传递过去;否则,调用class
的default constructor
,如果没有,就报错。 - 此外,
class
中的每一个virtual base class subobject
的偏移位置必须在执行期可被存取,以便在进行上述操作时移动this
指针。 - 如果
class object
时最底层的class
,其constructors
可能被调用。用以支持这一行为的机制必须被放进来。
- 如果
- 如果有,调用上一层的
base class constructors
,以base class
的声明顺序为顺序。- 如果
class
被列于member initialization list
中,那么任何显式指定的参数都应该被传递过去;否则,调用class
的default constructor
,如果没有,就报错。 - 如果
base class
是多重继承下的第二或后继的base class
,必须根据情况调整this
指针以指向正确的位置。
- 如果
- 如果有,指定
vptr
的初值,以便指向正确的virtual table
。 - 如果有,如果类中有
member
出现在member initialization list
或者含有一个default constructor
,按照它们在类中被声明的顺序依次进行初始化。 - 如果有,执行用户显式指定的代码部分,即构造函数的显式函数体。
继承体系下构造过程中需要注意的地方:
- 虚拟继承下的
virtual base class
构造:这种情况下,virtual base subobject
的构造总是由继承链中最底层的class
来否则。因此需要在构造函数中额外加入一个参数,指示该class
是否为most_derived
,如果是,则构造,否则,就跳过。 - 继承体系下对象的构造过程:令每一个
base class constructor
设定其对象的vptr
,使它指向相关的virtual table
,构造中的对象就可以严格而正确地变成”构造过程中所幻化出来的每一个class
“的对象。也即是说,一个PVertex
对象会先形成一个Point
对象,一个Point3d
对象,一个Vertex
对象,一个Vertex3d
对象,然后才成为一个PVertex
对象。 - 在一个
class
的constructor
的member initilization list
中调用该class
的一个虚函数,用其返回值作为member
初始化的参数:在原则是安全的,因为vptr
保证能够在member initilization list
被扩展之前,由编译器正确设定好;在语意上是不安全的,因为该虚函数本身可能依赖于尚未被设定初值的members
,这种做法并不推荐。 - 在一个
class
的constructor
的member initilization list
中调用class
的一个虚函数,用其返回值作为base class constructor
的参数:这种做法是不安全的,因为此时vptr
若不是未被设定好,就是指向错误的class
,这种情况绝对不允许。
对象复制语义学
Copy operator
的工作:一个copy operator
会在需要的情况下被编译器合成出来,然而当该合成的函数不能满足需要时,我们需要自己定义它。一般地,一个copy operator
需要负责复制自身的成员,并调用base class
的copy operator
来copy base subobject
。- 虚拟继承下的
copy operator
:这种情况下,由于不能抑制Base class
中对shared virtual base subobject
的copy
动作,因此上会发生shared virtual base subobject
的多重拷贝过程。建议做法是尽可能不要允许一个virtual base class
的拷贝操作,甚至不要再任何virtual base class
中声明数据。
析构语义学
一个由程序员定义的destructor
被扩展的方式类似constructor
被扩展的方式,但是顺序相反:
- 如果有,重设该
object
的vptr
指针,指向相关的virtual table
。 - 如果有,
destructor
的函数本体被执行,即用户定义的相关操作。 - 如果有,按照成员在
class
中被声明的顺序的相反顺序,调用member class objects
的destructor
。 - 如果有,按照
base class
声明顺序的相反顺序,调用任何直接的nonvirtual base classes
的destructor
。 - 如果有,假如讨论的是类是
most-derived
的,按照virtual base classes
被声明的顺序的相反顺序,调用其destructor
。
第6章 执行期语意学
对象的构造和析构
- 全局对象:
Global object
在程序编译时期可以被放置于data segment
中并且内容为0,但object
的constructor
的调用一直到程序启动时才会被执行。因此上,一个含有constructor
和destructor
的global object
需要静态初始化操作和内存释放操作。- 静态初始化操作通常会被统一集中起来放在
main
函数首部执行,而内存释放操作则会被统一集中起来放在main
函数尾部执行。一个良好的建议就是不要用那些需要静态初始化的global objects
。
- 局部静态对象:
- 局部静态对象也是存放在
data segment
当中,在所属函数首次被调用时进行初始化,在main
函数尾部与全局对象一起进行内存释放。
- 局部静态对象也是存放在
- 对象数组:
- 如果
class
不含有constructor
和destructor
,则所需要做的工作和一个内建类型数组一样多;否则,需要在定义时调用一个类似vec_new
的函数,将constructor
施行于各个元素身上,在释放时需要调用一个类似vec_delete
的函数,将destructor
施行于各个元素身上。 - 如果
class
含有一个以上的形式参数,且每个参数都含有默认值,那么它也可以作为数组的元素类型。编译器可能会构建一个不含参的stub constructor
,在函数内调用class
的constructor
,并将默认参数显式地传递过去。
- 如果
new和delete运算符
- 运算符
new
的操作事实上是通过两个步骤完成的:先通过适当的new
运算符函数实例,配置所需的内存;再为配置得来的对象调用constructor
设定初值。运算符delete
的操作也是类似:先调用对象的destructor
析构对象;再释放掉对象所占用的内存。(注:前提是object
有constructor
和destructor
,否则不用调用) - 针对数组的
new
跟delete
:如果class
含有constructor
和destructor
,需要在定义时调用一个类似vec_new
的函数,先为对象配置内存,再将constructor
施行于各个元素之上;在释放时需要调用一个类似vec_delete
的函数,先将destructor
施行于各个元素之上,再释放对象所占用的内存。 Placement operator new
的语意:其语意是在指定的内存位置上,调用constructor
构造一个对应class
的对象。而这个内存位置应该是预先已经开辟出来的:或者是该内存地址上的对象被析构而内存没有被释放;或者是一块刚开辟出来的未作任何处理的内存块。
临时性对象
- 临时性对象的摧毁时机:临时性对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤,该完整表达式造成临时对象的产生。
- 临时性对象的生命规则有两个例外:第一个例外发生在表达式被用来初始化一个
object
时,临时对象应该存留到object
的初始化操作完成为止,如用一个问号表达式的结果初始化一个string
对象;第二个例外是当一个临时性对象被一个reference
绑定时,临时对象将残留到reference
的生命结束或者临时对象的生命范畴结束,如将一个string
对象绑定到一个字符串上。
第7章 站在对象模型的尖端
Template
- 实例化:一个
template
会在定义了该template
一个实例对象时,进行相应的实例化,以便产生该实例对象的真正布局形式。然而该实例的member functions
只有在被使用的时候才要求它们被实例化。(其主要原因是:空间和时间效率的考虑;尚未实现的机能) - 名称决议法:一个编译器必须保持两个
scope contexts
:”scope of the template declaration”用以专注于一般的template class
;”scope of the template instantiation”用以专注于特定的实例。 Member Function
的实例化行为:- 编译器如何找出函数的定义?包含
template program text file
,就好像它是一个头文件一样;要求一个文件命名规则,如Point.h
的声明,其定义必被放在Point.C/Point.cpp
中。 - 编译器如何只实例化程序中用到的
member functions
?忽略这项要求,把一个已经实例化的class
的所有member functions
都产生出来;模拟链接操作,检测哪一个函数真正需要,只为它们产生实例。 - 编译器怎么组织
member definitions
在多个.o
文件中都被实例化?产生多个实例,然后从链接器中提供支持,只留下其中一个实例,其余都忽略;另一个方法就是由使用者来引导”模拟链接阶段”的实例化策略,决定哪些实例才是需求的。
- 编译器如何找出函数的定义?包含
异常处理
- 当一个
exception
发生时,编译系统必须完成以下事情:- 检验发生
throw
操作的函数。 - 决定
throw
操作是否发生才try
区段中。如果是,跳到3,否则,跳到4。 - 把
exception type
拿来和每一个catch
子句进行比较。如果比较吻合,就将控制流程交到catch
子句手上;否则跳到4。 - 首先摧毁所有的
active local objects
;然后从堆栈中将目前的函数”unwind
“掉;最后进行到程序堆栈的下一个函数中去,重复操作2~4。
- 检验发生
- 当一个
catch
子句捕获到一个异常时:- 如果
catch
是以object
方式捕获的,就会发生copy
动作,在catch
子句结束时,这个用以捕获异常的临时对象会被销毁掉。此时如果发生throw
操作,则是参考真正的exception object
。 - 如果
catch
是以reference
方式捕获的,任何对其做的改变,都会随着再次的throw
操作被繁殖到下一个catch
子句中。
- 如果
执行期类型识别
- 欲支持
type-safe downcast
的额外负担:- 需要额外的空间以存储类型信息,通常是一个指针,指向某个类型信息结点。
- 需要额外的时间以决定执行期的类型,因为这需要在执行期才能决定。
Type-safe dynamic cast
:- C++的
dynamic_cast
运算符可以在执行期决定真正的类型,如果downcast
是安全的,这个运算符会传回适当的转换过的指针;如果是不安全的,这个运算符会传回0。 - 这种动态转换的真正成本是:一个
type_info
会被编译器产生出来,其地址被放在virtual table
的第一个slot
内。而在执行期需要转换时,需要由object
的vptr
指针简介取得相应的type_info object
,才能保证安全转换。 - 当
dynamic_cast
运算符用于reference
时:如果reference
真正参考到适当的derived class
,downcast
会被执行而程序可以继续运行;否则,由于不能传回0,因此抛出一个bad_cast exception
。
- C++的
Typeid
运算符:- C++的
typeid
运算符传回一个类型为type_info
的const reference
,该class
定义了判断相等与否的equality
运算符。 - 事实上,
type_info
除过适用于多态类之外,也适用于内建类型以及非多态的使用者自定类型。区别在于,后者的type_info object
是静态取得,而非执行期取得。 - 使用
typeid
运算符,就可以配合static_cast
产生跟dynamic_cast
一样的效果。
- C++的