Effective-cpp-#27

Minimize casting

动机

C++的设计目标就是要保证“类型错误”不可能发生,使得程序能够“干净地”通过编译。

使用

一般来说,在C++中有两种转型——"old-style casts"和"new-style casts"。 * old:(T)expression或者T(expression); * new:

1
2
3
4
const_cast<T>(exp)
dynamic_cast<T>(exp)
reinterpret_cast<T>(exp)
static_cast<T>)exp

  1. const_cast:通常用来去除对象的常量性;
  2. dynamic_cast:主要执行“安全向下转型”,但会耗费大量的运行成本;
  3. reinterpret_cast:主要是执行低级转型,实际结果取决于编译器,所以其不可移植。例如将一个pointer to int转为int
  4. static_cast:用来强迫隐式转换,例如将non-const转为const,int转为double,同样也可以是void*转为typed指针,或者pointer to base转为pointer to derived


之所以要用新式转换,有两个原因:一是很容易能够在代码中辨认出来,便于debug;二是将转型动作窄化,编译器更可能诊断出错误运用。
至于什么时候用旧式转换——调用一个explicit构造函数讲一个对象传递给一个函数时。例如:

1
2
3
4
5
6
7
class Widget{
public:
explicit Widget(int size);
}
void doSomeWork(const Widget& w);
doSomeWork(Widget(15));
doSomeWork(Static_cast<Widget>(15));

往往,转型动作会令到编译器编译出运行期间不同执行的码。例如:

1
2
3
4
class Base{...};
class Derived: public Base{...};
Derived d;
Base* pd = &d;
我们可以看到,在这种情况下建立一个base class指针指向一个derived class对象,会有一个偏移量在运行期被施行于Derived指针身上,用以取得正确的Base 指针值.

另一个有趣的事情是,如果我们希望在derived classes内的virtual函数代码第一个动作先调用base class的对应函数。使用以下的方式,其实是错误的:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Window{
public:
virtual void onResize(){...}
...
};

class SpecialWindow: public Windos{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize();
}
...
}
其实这种行为,调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个"*this对象的base class成分"的暂时副本身上的onResize!


再来看看dynamic_cast,之所以需要dynamic_cast,通常是因为你只有一个“指向Base”的指针或引用,但希望在一个你认定为derived class对象身上执行derived class操作函数。
对于这种情况,有两个解决办法:
使用容器并在其中存储指向derived class对象的指针,并且尽量避免转型:
1
2
3
4
5
6
7
typedef std::vector<std::trl::shared_ptr<SpecialWindow> >VPSW;
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter)
(*iter)->blink();
但是这种方法,不能在同一个容器中存储指针“指向所有可能的Window派生类”。

另外一种做法就可以让你通过base class 接口处理“所有可能的各种Window派生类”,那就是在base class内提供virtual函数做你想对各个Window派生类做的事。当然,在base class只是提供一个“什么都不做”的缺省实现码。

建议

  • 如果可以,尽量避免转型,尤其是注重效率的代码中避免使用dynamic_cast;
  • 如果转型是必要的,试着将其隐藏在某个函数背后。客户随后直接调用该函数,而不需要将转型放进自己的代码内;
  • 宁愿使用新式转型,不要使用旧式转型;