一. 内容
-
C++规则的设计目标之一是,
保证类型错误绝不可能发生
。理论上如果你的代码很干净的通过编译,就表示它并不企图在任何对象身上执行任何不安全的,无意义的,荒谬的操作。你应该始终坚持这一点。 -
不幸的是,转型破坏了类型系统,这可能导致任何种类的错误,有些容易识别,有些则相当隐晦。
-
我们先回顾转型语法。C风格的转型:
(T)expression
。函数风格的转型:T(expression)
。两种形式并无差别,纯粹只是小括号摆放位置不一样而已,这是旧式转型。C++ 还提供四种新型转型
,常常被称为 new-style 或者C++ -style casts。const_cast<T>(expression) dynamic_cast<T>(expression) reinterpret_cast<T>(expression) static_cast<T>(expression)
各有各的用法。
- const_cast 通常用来将常对象的常量性转除。它也是
唯一一个可以移除常量性的转型操作符
。 - dynamic_cast 主要用来执行 base-derived 体系的安全向下转型。它是唯一无法由旧时语法执行的动作,也是唯一可能花费重大运行成本的转型动作。
- reinterpret_cast 用以执行
低级转型
,实际动作及结果可能取决于编译器,这也表示它不可被移植。比如 将一块被解释成 int 的 0x00636261 重新解释为 char 为 abc,除非编写底层代码,一般很少见。 - static_cast 用来
强制隐式转换
,例如将 non-const 转换成 const,或将 int 转换成 double。
- const_cast 通常用来将常对象的常量性转除。它也是
-
旧式语法虽然合法,但新式语法较受欢迎。原因:
- 它们很容易在代码中被辨识出来,可以简化找出类型系统在何处被破坏的过程。
- 各个转型动作的目标明确,编译器发现转型错误的可能性更高。举个例子,当你想将 对象的 const 移除,唯一可以使用的转型操作符只能是 const_cast。
-
请
理智使用新式转型
。转型不只是告诉编译器把某种类型视作另一种类型,往往还产出了实际的运行码。比如将一个 derived class 对象的指针赋值给 base class 对象的指针,在赋值时会进行隐式转换,这时会有一个偏移量加在 derived class指针上,用来获得正确的 base* 值。 -
关于 dynamic_cast 转型,它的
许多实现版本速度相当慢
,例如一个基于class名称的字符串比较,对于一个四重单继承的体系,可能会用到四次 strcmp的调用。深度继承或者多重继承的成本更高。这样实现不是没有原因,它们必须支持动态连接。之所以需要 dynamic_cast 转型,通常是因为你手上只有一个指向 base 的指针或者引用,但你想使用derived class的操作函数,需要进行转换。有两种方法可以避免这个问题:-
使用容器存储直接指向 derived class 对象的指针,通常是智能指针,参见条款13。
-
在base class 提供 virtual 函数做那些想对 derived class 做的事情。
绝对要避免以下代码:class Window { public: virtual ~Window(); }; class WindowOne : public Window { }; class WindowTwo : public Window { }; class WindowThree : public Window { }; inline void Try(Window* Window) { if (WindowOne* One = dynamic_cast<WindowOne*>(Window)) { //... } else if (WindowTwo* Two = dynamic_cast<WindowTwo*>(Window)) { //... } else if (WindowThree* Three = dynamic_cast<WindowThree*>(Window)) { //... } }
这样产生出来的代码又大又慢,而且基础不稳。因为每次 Window class 继承体系一有改变,所有这类代码都需要重新编写。这样的代码应该总是由某些基于 virtual 函数调用的东西取而代之。
另外注意,dynamic_cast 只能作用于具备多态性质的base class,意味着
base class 至少具有一个 virtual 函数
。
-
-
当然,
优良的C++代码很少使用转型
,但要说完全摆脱它们又太不切实际,所以我们应该尽量隔离转型行为,通常是将它们隐藏在某个private函数中。
二. 总结
- 如果可以,尽量避免转型,特别是注重效率的代码中避免 dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。
- 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放入他们自己的代码内。
- 宁可使用C++ - syle新式转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。