条款27:尽量少做转型动作(Effective C++)
C++规则设计的目标之一是,保证类型错误决不可能发生。理论上如果你的程序很干净的通过编译,就表示它并不企图在任何对象身上执行任何不安全,无意义,愚蠢荒谬的操作。这是一个及其具有价值的保证,不要轻易放弃它。
但是在很多种情况下,我们不得不进行转型操作,转型操作破坏了类型系统。这可能会导致任何可能种类的麻烦,有些容易辨识,但是有些可能会很隐晦。所以在需要进行转型操作的时候一定要慎重,尽量通过设计避免不必要的转型操作。
类型转换的形式
首先我们回顾一下类型转换的语法,因为通常有三种不同的形式,可写出相同的类型转换动作。
- C风格类型转换: (T)expression //将expression转换为类型T
- 函数式风格类型转换: T(expression) //同上
上面的两种形式并无差别,纯粹只是把小括号摆放的位置不同而已,我们称上述两种转为为”旧式转型”(old style cast)。
C++还提供四中新式转型(new style):
- const_cast< T >(expression)
- dynamic_cast< T >(expression)
- reinterpret_cast< T >(expression)
static_cast< T >(expression)
const_cast通常被用来将对象的常量性移除(cast away the constness)。它也是唯一有此能力的C++-style转型操作符。
- dynamic_cast主要用来执行类型向下转型(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一一个可能耗费重大运行成本的转型动作。
- reinterpret_cast 意图执行低级转型,实际动作及结果可能取决于编译器。这也就表示它不可移植,例如将一个pointer to int 转型为int。这一类型转换在低级代码以外很少见。
- static_cast 用来强迫隐式转换(implicit conversion),例如将non-const对象转换为const对象,或者将int转换为double等等。也可以用来执行上述多种转换的反向转换,例如将void指针转换为type指针,将pointer to derive 转化为point to bas。但是无法将const转换为non-const。
dynamic_cast与static_cast详解
static_cast是用来强迫隐式类型转换,它可以用于1.基本数据类型以及指针之间的转换;2.类层次中基类与子类成员函数指针的转换;3.类层次结构中基类与子类指针或者引用之间的转换。
dynamic_cast可以用于1.继承关系中类指针或者引用之间的转换;2.包含虚函数之间对象指针的转换3.以及保证转换的安全性。
static_cast
用于基本数据类型转换和指针之间的转换
|
|
类层次中基类与子类成员函数指针的转换
|
|
类层次结构中基类与子类指针或者引用之间的转换
上行转换:子类指针或引用转换为基类的指针或引用 —安全
下行转换:基类的指针或者引用转换为子类的指针或引用 —危险(避免这样做)
dynamic_cast
继承关系的类指针对象或者引用之间的转换
若积累中没有虚函数,使用dynamic_cast可以将子类的指针或引用转换为基类的指针或引用,与static_cast用法相同,不同的是,这个时候使用dynamic_cast将基类指针转换为子类指针的时候会出现编译错误(static_cast不会,但是很危险)。
包含有虚函数之间的对象指针的转换
使用dynamic_cast将基类指针转换为子类指针的时候并不是永远有效:只有基类指针本身指向的就是一个派生类对象的时候有效。其他时候结果为NULL;
dynamic_cast转换的安全性
当涉及到基类和派生类对象之间的转换的时候,总使用dynamic_cast会避免很多错误,它是安全的,但是它会给程序运行带来巨大的开销。
当子类指针转换为基类指针的时候,两种转型都OK,dynamic_cast开销较大。
当基类指针转换为派生类指针的时候,若基类中没有虚函数,static_cast不会报错,但是做法很危险,dynamic_cast编译不通过。当含有虚函数的时候,若基类指针没有指向派生类,这个时候会返回NULL,所以也是安全的。
虚函数对于dynamic_cast转换的作用
为什么dynamic_cast转换类指针的时候需要虚函数呢?
dynamic_cast转换是在运行时进行转换,运行时转换就需要知道类对象的信息(继承关系等)。
在运行时或者这个信息的是虚函数表指针,通过这个指针可以获取到该类对象的所有的虚函数,包括父类的。因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道类对象的父类,在转换的时候就可以用来判断对象有无继承关系。
所以虚函数对于正确的基类指针转换为子类指针是非常重要的。
effective的三点建议
- 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
- 如果转型是必要的,试着将它隐藏与某个函数的背后。客户随后可以调用该函数,而不需要将转型放到他们自己的代码中。
- 宁可使用C++-style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌