Minimize casting
动机
C++的设计目标就是要保证“类型错误”不可能发生,使得程序能够“干净地”通过编译。
使用
一般来说,在C++中有两种转型——"old-style casts"和"new-style casts"。 *
old:(T)expression或者T(expression); * new: 1
2
3
4const_cast<T>(exp)
dynamic_cast<T>(exp)
reinterpret_cast<T>(exp)
static_cast<T>)exp
- const_cast:通常用来去除对象的常量性;
- dynamic_cast:主要执行“安全向下转型”,但会耗费大量的运行成本;
- reinterpret_cast:主要是执行低级转型,实际结果取决于编译器,所以其不可移植。例如将一个pointer to int转为int;
- static_cast:用来强迫隐式转换,例如将non-const转为const,int转为double,同样也可以是void*转为typed指针,或者pointer to base转为pointer to derived
之所以要用新式转换,有两个原因:一是很容易能够在代码中辨认出来,便于debug;二是将转型动作窄化,编译器更可能诊断出错误运用。
至于什么时候用旧式转换——调用一个explicit构造函数讲一个对象传递给一个函数时。例如:
1 | class Widget{ |
往往,转型动作会令到编译器编译出运行期间不同执行的码。例如:
1
2
3
4class Base{...};
class Derived: public Base{...};
Derived d;
Base* pd = &d;
另一个有趣的事情是,如果我们希望在derived
classes内的virtual函数代码第一个动作先调用base
class的对应函数。使用以下的方式,其实是错误的: 1
2
3
4
5
6
7
8
9
10
11
12
13class Window{
public:
virtual void onResize(){...}
...
};
class SpecialWindow: public Windos{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize();
}
...
}
再来看看dynamic_cast,之所以需要dynamic_cast,通常是因为你只有一个“指向Base”的指针或引用,但希望在一个你认定为derived
class对象身上执行derived class操作函数。
对于这种情况,有两个解决办法:
使用容器并在其中存储指向derived
class对象的指针,并且尽量避免转型: 1
2
3
4
5
6
7typedef std::vector<std::trl::shared_ptr<SpecialWindow> >VPSW;
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter)
(*iter)->blink();
另外一种做法就可以让你通过base class
接口处理“所有可能的各种Window派生类”,那就是在base
class内提供virtual函数做你想对各个Window派生类做的事。当然,在base
class只是提供一个“什么都不做”的缺省实现码。
建议
- 如果可以,尽量避免转型,尤其是注重效率的代码中避免使用dynamic_cast;
- 如果转型是必要的,试着将其隐藏在某个函数背后。客户随后直接调用该函数,而不需要将转型放进自己的代码内;
- 宁愿使用新式转型,不要使用旧式转型;