不幸是强制转型破坏了类型系统它会引起各种各样麻烦其中些容易被察觉另些则格外地微妙如果你从 CJava或 C# 转到 C请定注意强制转型在那些语言中比在 C 中更有必要危险也更少但是 C 不是 C也不是 Java也不是 C#在这语言中强制转型是个你必须全神贯注才可以靠近特性
我们就从回顾强制转型语法开始对于同样强制转型通常有 3种区别写法C 风格(C-style)强制转型如下:
(T) expression // cast expression to be of type T
风格(Function-style)强制转型使用这样语法:
T(expression) // cast expression to be of type T
这两种形式的间没有本质上区别它纯粹就是个把括号放在哪问题我把这两种形式称为旧风格(old-style)强制转型
C 同时提供了 4种新强制转型形式(通常称为新风格或 C 风格强制转型):
const_cast(expression)
dynamic_cast(expression)
reerpret_cast(expression)
_cast(expression)
每种适用于特定目:
·const_cast 般用于强制消除对象常量性它是唯能做到这点 C 风格强制转型
·dynamic_cast 主要用于执行“安全向下转型(safe downcasting)”也就是说要确定个对象是否是个继承体系中个特定类型它是唯不能用旧风格语法执行强制转型也是唯可能有重大运行时代价强制转型(过会儿我再提供细节)
·reerpret_cast 是特意用于底层强制转型导致实现依赖(implementation-dependent)(就是说不可移植)结果例如将个指针转型为个整数这样强制转型在底层代码以外应该极为罕见在本书中我只用了次而且还仅仅是在讨论你应该如何为裸内存(raw memory)写个调谐分配者(debugging allocator)时候
·_cast 可以被用于强制隐型转换(例如non-const 对象转型为 const 对象(就像 Item 3 中) 转型为 double等等)它还可以用于很多这样转换反向转换(例如void* 指针转型为有类型指针基类指针转型为派生类指针)但是它不能将个 const 对象转型为 non-const 对象(只有 const_cast 能做到)
旧风格强制转型依然合法但是新形式更可取首先在代码中它们更容易识别(无论是人还是像 grep 这样工具都是如此)这样就简化了在代码中寻找类型系统被破坏地方过程第 2更精确地指定每个强制转型目使得编译器诊断使用成为可能例如如果你试图使用个 const_cast 以外新风格强制转型来消除常量性你代码将无法编译
当我要个 explicit 构造用来传递个对象给个时候大概就是我仅有使用旧风格强制转换时候例如:
Widget {
public:
explicit Widget( size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); // create Widget from
// with function-style cast
doSomeWork(_cast(15)); // create Widget from
// with C-style cast
由于某种原因有条不紊对象创建感觉上不像个强制转型所以在这个强制转型中我多半会用风格强制转型代替 _cast反过来说在你写出那些导致核心崩溃(core dump)代码时你通常都感觉你有恰当原因所以你最好忽略你感觉并始终都使用新风格强制转型
很多员认为强制转型除了告诉编译器将种类型看作另种的外什么都没做但这是任何种类类型转换(无论是通过强制转型显式还是编译器添加隐式)都会导致运行时可执行代码例如在这个代码片断中
x, y;
...
double d = _cast(x)/y; // divide x by y, but use
// floating po division
x 到 double 强制转型理所当然要生成代码在大多数系统架构中个 底层表示和 double 区别这可能还不如何令人吃惊但是下面这个例子可能会让你稍微开下眼:
Base { ... };
Derived: public Base { ... };
Derived d;
Base *pb = &d; // implicitly convert Derived* → Base*
这里我们只是创建了个指向派生类对象基类指针但是有时候这两个指针值并不相同在当前情况下会在运行时在 Derived* 指针上应用个偏移量以得到正确 Base* 指针值
这后个例子表明个单对象(例如个类型为 Derived 对象)可能会有不止个地址(例如它被个 Base* 指针指向地址和它被个 Derived* 指针指向地址)这在 C 中就不会发生也不会在 Java 中发生也不会在 C# 中发生它仅在 C 中发生实际上如果使用了多继承则定会发生但是在单继承下也会发生和其它事情合在起就意味着你应该总是避免对 C 如何摆放事物做出假设你当然也不应该基于这样假设执行强制转型例如将个对象地址强制转型为 char* 指针然后对其使用指针运算这几乎总是会导致未定义行为
但是请注意我说个偏移量是“有时”被需要对象摆放思路方法和他们地址计算思路方法在区别编译器的间有所变化这就意味着仅仅你“我知道事物是如何摆放”而使得强制转型能工作在个平台上并不意味着它们也能在其它平台工作这个世界被通过痛苦道路学得这条经验可怜员所充满 有关强制转型件有趣事是很容易写出看起来对(在其它语言中也许是对)实际上错东西例如许多应用框架(application framework)要求在派生类中实现虚成员时要首先它们基类对应物假设我们有个 Window 基类和个 SpecialWindow 派生类它们都定义了虚 _disibledevent=> iter != winPtrs.end; // uses dynamic_cast
iter) {
(SpecialWindow *psw = dynamic_cast(iter->get))
psw->blink;
}
设法用如下思路方法代替:
typedef std::vector > VPSW;
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin; // better code: uses
iter != winPtrs.end; // no dynamic_cast
iter)
(*iter)->blink;
当然这个思路方法不允许你在同个容器中存储所有可能 Window 派生类指针为了和区别窗口类型起工作你可能需要多个类型安全(type-safe)容器
个候选思路方法可以让你通过个基类接口操控所有可能 Window 派生类就是在基类中提供个让你做你想做事情虚例如尽管只有 SpecialWindows 能 blink在基类中声明这个并提供个什么都不做缺省实现或许是有意义:
Window {
public:
virtual void blink {} // default impl is no-op;
... // see Item 34 for why
}; // a default impl may be
// a bad idea
SpecialWindow: public Window {
public:
virtual void blink { ... }; // in this , blink
... // does something
};
typedef std::vector > VPW;
VPW winPtrs; // container holds
// (ptrs to) all possible
... // Window types
for (VPW::iterator iter = winPtrs.begin;
iter != winPtrs.end;
iter) // note lack of
(*iter)->blink; // dynamic_cast
无论哪种思路方法——使用类型安全容器或在继承体系中上移虚——都不是到处适用但在很多情况下它们提供了 dynamic_casting 的外另个可行候选思路方法当它们可用时你应该加以利用
你应该绝对避免件东西就是包含了极联 dynamic_casts 设计也就是说看起来类似这样任何东西:
Window { ... };
... // derived es are d here
typedef std::vector > VPW;
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin; iter != winPtrs.end; iter)
{
(SpecialWindow1 *psw1 = dynamic_cast(iter->get)) { ... }
(SpecialWindow2 *psw2 = dynamic_cast(iter->get)) { ... }
(SpecialWindow3 *psw3 = dynamic_cast(iter->get)) { ... }
...
}
这样 C 会生成代码又大又慢而且很脆弱每次 Window 类继承体系发生变化所有这样代码都要必须被检查以确认是否需要更新(例如如果增加了个新派生类在上面极联中或许就需要加入个新条件分支)看起来类似这样代码应该总是用基于虚某种东西来替换 好 C 极少使用强制转型但在通常情况下完全去除也不实际例如从 到 double 强制转型就是对强制转型合理运用虽然它并不是绝对必要(那些代码应该被重写声明个新类型为 double 变量并用 x 值进行化)就像大多数可疑结构成分强制转型应该被尽可能地隔离典型情况是隐藏在内部用接口保护者远离内部污秽工作
Things to Remember
·避免强制转型随时应用特别是在性能敏感代码中应用 dynamic_casts如果个设计需要强制转型设法开发个没有强制转型侯选方案
·如果必须要强制转型设法将它隐藏在个中客户可以用那个来代替在他们自己代码中加入强制转型
·尽量用 C 风格强制转型替换旧风格强制转型它们更容易被注意到而且他们做事情也更加明确
最新评论