Tips: This article based on Scott Meyers's <<Effective C++>> article 27: Minimize Casting
C++规则的设计目标之一,是保证"类型错误"绝对不可能发生。理论上你的程序可以很“干净”的通过编译,就表示它并不企图在任何对象身上执行任何不安全的,无意义的,愚蠢荒谬的操作。这是一个极具价值的保证,可别草率的放弃。
但是,转型(casting)却破坏了类型系统(type system)。
C++提供了三中不同类型的转化风格:
- C风格的转换: (T)expression
- 函数风格的转换: T(expression)
- 新式风格的转换(new-style or C++ style casts)
C++提供了4种形式的新式转换,每种形式的转换如下:
1)const_cast<T>(expression): const_cast 通常被用来将对象的常量性转除(cast away the constness)。它也是唯一具有此能力的的C++-style转型操作。
2)dynamic_cast<T>(expression): dynamic_cast 主要用来执行“安全向下转型”(Safe downcasting), 也就是用来决定某对象是否归属继承体系中的某个类型。
它是唯一一个无法用旧式语法执行的动作,也是唯一可能消耗重大运行成本的转型动作!(注:旧式风格的转换无法实现父类对象到子类对象的转换)
3) reinterpret_cast<T>(expression): 意图执行低级转型,实际动作(及结果)可能取决于编译器,这就表示它不可移植。这个转换多用在低级代码中。
4)static_cast<T>(expression):用来强迫隐式转换(implicit conversions), 例如将non-const对象转换为const对象,或将int转换位double等等。它也可以来
执行上述多种转换的反向转换。例如,其可以将void型的指针转换位typed型的指针,将pointer-to-base 转换为pointer-to-derived。但它无法将const转换成no-const,这
只有const_cast才能办得到!
许多程序员相信,转型其实什么都没有做,只是告诉编译器将一种类型视为另一种类型,这是错误的观念! 下面我们通过一个实例来验证转型期间编译器确实是做了什么!
1 #include <iostream>
2 #include <string>
3
4 using namespace std;
42
43 /* 多重继承 */
44 class Base1
45 {
46 public:
47 Base1(const string& aName)
48 {
49 name = aName;
50 }
51
52 string getName()
53 {
54 return name;
55 }
56 private:
57 string name;
58 };
59
60 class Base2
61 {
62 public:
63 Base2(const string& aAddress)
64 {
65 address = aAddress;
66 }
67
68 string getAddress()
69 {
70 return address;
71 }
72 private:
73 string address;
74 };
75
76 class Drive : public Base1,public Base2
77 {
78 public:
79 Drive(const string& aName,const string& aAddress)
80 :Base1(aName),Base2(aAddress)
81 {
82
83 }
84 };
85
86 int main(void)
87 {
88 Drive drive_obj("jiang heng","fudan university");
89 Base1* base1_pt = &drive_obj;
90 Base2* base2_pt = &drive_obj;
91 Drive* drive_pt = &drive_obj;
92 cout<<"The Address of base1_pt: "<<base1_pt<<endl;
93 cout<<"The Address of base2_pt: "<<base2_pt<<endl;
94 cout<<"The Address of drive_pt: "<<drive_pt<<endl;
95 return 0;
96
结果:
The Address of base1_pt: 0x7fffc9c64290
The Address of base2_pt: 0x7fffc9c64298
The Address of drive_pt: 0x7fffc9c64290
上述的实例表明C++中单一对象(例如上面的Drive对象)可以有一个以上的地址(比如上面Base2*类型的地址和Drive*类型的地址):
由此得到一个重要的结论对象的布局方式和它们的地址计算方式随着编译器的不同而不同,那意味着“由于知道对象如何布局”而设计的转型,
在某一平台上行得通,而在其他平台上不一定行得通。
关于转型的一个重要事实是你可能因此写出许多是是而非的代码,下面就是这样的一个实例:
1 #include <iostream>
2
3 using namespace std;
4
5 class Window
6 {
7 public:
8 Window(const int& wd,const int& hg)
9 :width(wd),height(hg)
10 {
11
12 }
13
14 int getWidth()
15 {
16 return width;
17 }
18
19 int getHeight()
20 {
21 return height;
22 }
23
24 virtual void onResize()
25 {
26 width += 100;
27 height += 200;
28 }
29
30 void printWindowMsg()
31 {
32 cout<<"The height of the window: "<<height<<endl;
33 cout<<"The width of the window: "<<width<<endl;
34 }
35 private:
36 int width;
37 int height;
38 };
39
40 class SpecialWindow : public Window
41 {
42 public:
43 SpecialWindow(const int& wd,const int& hg,const int& cor)
44 :Window(wd,hg)
45 {
46 color = cor;
47 }
48
49 virtual void onResize()
50 {
51 static_cast<Window>(*this).onResize();
52 color += 1;
53 }
54
55 void printWindowMsg()
56 {
57 Window::printWindowMsg();
58 cout<<"The color of this window is: "<<color<<endl;
59 }
60 private:
61 int color;
62 };
63
64 int main(void)
65 {
66 SpecialWindow spwind(20,30,2);
67 spwind.onResize();
68 spwind.printWindowMsg();
69 return 0;
70 }
71
结果:
The height of the window: 30
The width of the window: 20
The color of this window is: 3
static_cast<Window>(*this).onResize();
上面这条语句将*this转型成Window,对函数onResize的调用因此调用了Window::onResize。但其调用的并不是当前对象上的函数,而是稍早转型
动作所创建的"*this对象之base class成分"的暂时副本上的OnResize!
将上面的语句改成:
Window::onResize();
结果:
The height of the window: 230
The width of the window: 120
The color of this window is: 3
显然达到了我们想要的效果!
上面这个例子说明了,如果你在程序中遇到了转型,那么这活脱脱就是一个警告信号!
tips: 出了要对一般的转型保持机敏和猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏和猜疑!
只所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但是你的手上只有一个"指向base"的pointer或reference,
你只能靠它们来处理对象!
下面是结局上述问题的一般的做法:
- 使用容器并在其中存储直接指向derived class对象的指针(通常是智能指针),这样就消除了"通过base class接口处理对象"的需要。
- 通过base class接口处理"所有可能之各种Window派生类",即在base class内提供virtual函数做你想对各个Window派生类想做的事。
优秀的C++代码很少使用转型,但要说完全摆脱它门又是不切实际的。