Effective C++ 条款27_尽量少做转型动作_非同凡响

Minimize casting

C++ 规则的设计目标之一是,保证 “ 类型错误 ” 绝不可能发生。理论上如果你的程序很 “ 干净地 ” 通过编译,那么恭喜你,这表示它并不企图在任何对象上执行任何不安全、无意义、愚蠢荒谬的操作。

不幸的是,转型(casts)破坏了类型转换系统(type system)。那可能导致任何种类的麻烦,有些容易辨识,有些则非常隐晦。在 C++ 中转型是一个你会想带着极大尊重去亲近的一个特性。

旧式转型 类C风格:

(T)expression
T(expression)

C++ 提供四种新式转型:

const_cast< T >(expression)
dynamic_cast< T >(expression)
reinterpret_cast< T >(expression)
static_cast< T >(expression)

各有各的不同:

☆ const_cast 通常被用来将对象的常量性转除。它也是唯一有此能力的 C++ -style 转型操作符。

☆ dynamic_cast 主要用来执行 “ 安全向下转型 ” ,也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

☆ reinterpret_cast 意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个 pointer to int 转型为 一个 int。这一类转型在低级代码以外很少见。

☆ static_cast 用来强迫隐式转换,例如将 non-const 对象转为 const 对象,或将 int 转为 double 等等。它也可以用来执行上述多种转换的反向转换,例如将 void* 指针转为 typed 指针,将 pointer-to-base 转为 pointer-to-derived。但它无法将 const 转为 non-const——这个只有 const_cast 才办得到。

旧的转型仍然合法,但新的转型较受欢迎,原因:
① 它们很容易在代码中被识别出来(不论是人工辨识还是使用工具如 grep),因而得以简化 “ 找出类型系统在哪个地点被破坏 ” 的过程。
② 各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用。
e,g, 如果你打算将常量性(constness)去掉,除非使用新式转型中的 const_cast 否则无法通过编译。

这里给出唯一可能更看好旧式转型的时机,当你要调用一个 explicit 构造函数将一个对象传递给一个函数时:

class Widget {
public:
	explicit Widget(int size);
	...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15));					// 以一个 int 加上 “ 函数风格 ” 
										// 的转型动作创建一个 Widget。
doSomeWork(static_cast<Widget>(15));	// 以一个 int 加上 “ C++ ” 风格
										// 的转型动作创建一个 Widget

从某个角度来说,蓄意的 “ 对象生成 ” 动作感觉不怎么像 “ 转型 ”,所以我很可能使用函数风格的转型动作而不使用 static_cast。但始终理智地使用新型转型是最好的。

许多程序员认为,转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型,但这是错误的观念。任何一个类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出 运行期间执行的代码。
例:

int x, y;
...
double d = static_cast<double>(x) / y;		// x / y 用浮点数表示

将 int x 转型为 double 几乎肯定会产生一些代码,因为在大部分计算器体系结构中,int 的底层表述不同于 double 的底层表述。这或许不会令你惊讶,且看下面这个例子:

class Base { ... };
class Derived: public Base { ... };
Derived d;
Base* p = &d;		// 隐喻地将 Derived* 转换为 Base*

这里我们不过是建立一个 base class 指针指向一个 derived class 对象,但有时候上述两个指针值并不相同。这种情况下会有个偏移量(offset)在运行期被施于 Derived* 指针身上,用以取得正确的 Base* 指针值。

上述栗子表明,单一对象(例如一个类型为 Derived 的对象)可能拥有一个以上的地址(例如 “ 以 Base* 指向它 ” 时的地址和 “ 以 Derived* 指向它 ” 时的地址)。虽然这可能有其他意涵,但至少意味你通常应该避免做出 “ 对象在 C++ 中如何如何布局 ” 的假设。当然更不应该以此假设为基础执行任何转型操作。例如,将对象地址转型为 char* 指针然后在它们身上进行指针算术,几乎总是会导致无定义(不明确)行为。

但请注意,我说的是有时候需要一个偏移量。对象的布局方式和它们的地址计算方式随编译器的不同而不同,那意味 “ 由于知道对象如何布局 ” 而设计的转型,在某一平台行得通,在其他平台并不一定行得通。这个世界有许多悲惨的程序员,他们历经千辛万苦才学到这堂课。

请记住:

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免 dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。
  • 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。
  • 宁可使用 C++ -style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值