static_cast, dynamic_cast, reinterpret_cast, const_cast difference comparison

http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html

Implicit conversion

short a=2000;

int b;

b=a;

Short is two bytes, int is four bytes, the conversion from short to int is a widening conversion (the number of bits increases), and the compiler does not warn, as shown in the following figure. Widening conversions (such as char to int, int to long long, int to float, float to double, int to double, etc.) constitute implicit conversions, and the compiler allows direct conversions.

But if the reverse

double a=2000;

short b;

b=a;

At this time, the conversion from an 8-byte double type to a 2-byte short type variable is a narrowing conversion, and the compiler will issue a warning, as shown below, to remind the programmer that data may be lost. However, it should be noted that for some implicit conversions, the compiler may not give a warning, such as int to short, but data overflow still occurs.

 

C style explicit conversion

 

It is very simple to remove the above waring. Programmers familiar with the C language know that there are two simple ways of writing (C-style conversion and function-style conversion):

double a=2000.3;

short b;

b = (short) a;    // c-like cast notation

b = short (a);    // functional notation 

As shown in the figure below, the warning is gone at this time

This explicit conversion method is simple and intuitive, but it is not safe. An example of a parent class and a child class is as follows:

复制代码
复制代码
 1 // class type-casting
 2 #include <iostream>
 3 using namespace std;
 4 
 5 class CDummy {
 6 float i,j;
 7 CDummy():i(100),j(10){}
 8 };
 9 
10 class CAddition:public CDummy
11 {
12     int *x,y;
13   public:
14     CAddition (int a, int b) { x=&a; y=b; }
15     int result() { return *x+y;}
16 };
17 
18 int main () {
19   CDummy d;
20   CAddition * padd;
21   padd = (CAddition*) &d;
22   cout << padd->result();
23   return 0;
24 }
复制代码
复制代码

编译器不报任何错,但运行结果出错,如下图所示:

究其原因,注意这一句:padd = (CAddition*) &d;

此时父类的指针&d被C风格转换方式强制转成了子类的指针了,后面调用了子类的方法result,需要访问*x,但指针指向的对象本质还是 父类的,所以x相当于父类中的i,y相当于父类中的j,*x相当于*i,但i是float型变量(初始化为100),不是地址,所以出错,如果程序员正是 鲁莽地对这个地址指向的内存进行写入操作,那将可能会破坏系统程序,导致操作系统崩溃!

这里有一个重要概念,CAddition*是子类的指针,它的变量padd可以调用子类的方法,但是它指向的是父类的对象,也就是说padd指向的内存空间里存放的是父类的成员变量。深入地说,数据在内存中是没有“类型”一说的,比如0x3F可能是字符型,也可能是整型的一部分,还可能是地址的一部分。我们定义的变量类型,其实就是定义了数据应该“被看成什么”的方式。

因此padd类指针实质是定义了取值的方式,如padd->x就是一并取出内存空间里的0号单元至3号单元的值(共4个字节),将其拼成32 位并当作指针,padd->y则取出内存空间里的4号单元至7号单元(共4个字节),将其拼成32位并当作int型变量。但实际上padd指向的是 父类的对象,也就是前4个字节是float型变量,后4个字节也是float型变量。

从这里可以看出,程序员的这种转换使编译器“理解”出错,把牛当成马了。

从上可见,用C风格的转换其实是不安全的,编译器无法看到转换的不安全。

 

上行转换(up-casting)与下行转换(down-casting)

 

看到这个,读者可能会问,哪些转换不安全?根据前面所举的例子,可以看到,不安全来源于两个方面:其一是类型的窄化转化,会导致数据位数的丢失;其 二是在类继承链中,将父类对象的地址(指针)强制转化成子类的地址(指针),这就是所谓的下行转换。“下”表示沿着继承链向下走(向子类的方向走)。

类似地,上行转换的“上”表示沿继承链向上走(向父类的方向走)。

我们给出结论,上行转换一般是安全的,下行转换很可能是不安全的。

为什么呢?因为子类中包含父类,所以上行转换(只能调用父类的方法,引用父类的成员变量)一般是安全的。但父类中却没有子类的任何信息,而下行转换会调用到子类的方法、引用子类的成员变量,这些父类都没有,所以很容易“指鹿为马”或者干脆指向不存在的内存空间。

值得一说的是,不安全的转换不一定会导致程序出错,比如一些窄化转换在很多场合都会被频繁地使用,前提是程序员足够小心以防止数据溢出;下行转换关键看其“本质”是什么,比如一个父类指针指向子类,再将这个父类指针转成子类指针,这种下行转换就不会有问题。

 

针对类指针的问题,C++特别设计了更加细致的转换方法,分别有:

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

可以提升转换的安全性。

 

static_cast <new_type> (expression) 静态转换

 

静态转换是最接近于C风格转换,很多时候都需要程序员自身去判断转换是否安全。比如:

double d=3.14159265;

int i = static_cast<int>(d);

 

但static_cast已经有安全性的考虑了,比如对于不相关类指针之间的转换。参见下面的例子:

复制代码
复制代码
 1 // class type-casting
 2 #include <iostream>
 3 using namespace std;
 4 
 5 class CDummy {
 6     float i,j;
 7 };
 8 
 9 class CAddition {
10     int x,y;
11   public:
12     CAddition (int a, int b) { x=a; y=b; }
13     int result() { return x+y;}
14 };
15 
16 int main () {
17   CDummy d;
18   CAddition * padd;
19   padd = (CAddition*) &d;
20   cout << padd->result();
21   return 0;
22 }
复制代码
复制代码

这个例子与之前举的例子很像,只是CAddition与CDummy类没有任何关系了,但main()中C风格的转换仍是允许的padd = (CAddition*) &d,这样的转换没有安全性可言。

 

如果在main()中使用static_cast,像这样:

复制代码
复制代码
1  int main () {
2    CDummy d;
3    CAddition * padd;
4    padd = static_cast<CAddition*> (&d);
5    cout << padd->result();
6    return 0;
7 }
复制代码
复制代码

 

编译器就能看到这种不相关类指针转换的不安全,报出如下图所示的错误:

注意这时不是以warning形式给出的,而直接是不可通过编译的error。从提示信息里可以看到,编译器说如果需要这种强制转换,要使用reinterpret_cast(稍候会说)或者C风格的两种转换。

总结一下:static_cast最接近于C风格转换了,但在无关类的类指针之间转换上,有安全性的提升。

 

dynamic_cast <new_type> (expression) 动态转换

 

动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件,其一是要求new_type为指针或引用,其二是下行转换时要求基类是多态的(基类中包含至少一个虚函数)。

看一下下面的例子:

复制代码
复制代码
 1 #include <iostream>
 2 using namespace std;
 3 class CBase { };
 4 class CDerived: public CBase { };
 5 
 6 int main()
 7 {
 8 CBase b; CBase* pb;
 9 CDerived d; CDerived* pd;
10 
11 pb = dynamic_cast<CBase*>(&d);     // ok: derived-to-base
12 pd = dynamic_cast<CDerived*>(&b);  // wrong: base-to-derived 
13 }
复制代码
复制代码

在最后一行代码有问题,编译器给的错误提示如下图所示:

把类的定义改成:

class CBase { virtual void dummy() {} };

class CDerived: public CBase {};

再编译,结果如下图所示:

编译都可以顺利通过了。这里我们在main函数的最后添加两句话:

cout << pb << endl;

cout << pd << endl;

输出pb和pd的指针值,结果如下:

我们看到一个奇怪的现象,将父类经过dynamic_cast转成子类的指针竟然是空指针!这正是dynamic_cast提升安全性的功能,dynamic_cast可以识别出不安全的下行转换,但并不抛出异常,而是将转换的结果设置成null(空指针)。

再举一个例子:

复制代码
复制代码
 1 #include <iostream>
 2 #include <exception>
 3 using namespace std;
 4 
 5 class CBase { virtual void dummy() {} };
 6 class CDerived: public CBase { int a; };
 7 
 8 int main () {
 9   try {
10     CBase * pba = new CDerived;
11     CBase * pbb = new CBase;
12     CDerived * pd;
13 
14     pd = dynamic_cast<CDerived*>(pba);
15     if (pd==0) cout << "Null pointer on first type-cast" << endl;
16 
17     pd = dynamic_cast<CDerived*>(pbb);
18     if (pd==0) cout << "Null pointer on second type-cast" << endl;
19 
20   } catch (exception& e) {cout << "Exception: " << e.what();}
21   return 0;
22 }
复制代码
复制代码

输出结果是:Null pointer on second type-cast

两个dynamic_cast都是下行转换,第一个转换是安全的,因为指向对象的本质是子类,转换的结果使子类指针指向子类,天经地义;第二个转换是不安全的,因为指向对象的本质是父类,“指鹿为马”或指向不存在的空间很可能发生!

最后补充一个特殊情况,当待转换指针是void*或者转换目标指针是void*时,dynamic_cast总是认为是安全的,举个例子:

复制代码
复制代码
 1 #include <iostream>
 2 using namespace std;
 3 class A {virtual void f(){}};
 4 class B {virtual void f(){}};
 5 
 6 int main() {
 7     A* pa = new A;
 8     B* pb = new B;
 9     void* pv = dynamic_cast<void*>(pa);
10     cout << pv << endl;
11     // pv now points to an object of type A
12 
13     pv = dynamic_cast<void*>(pb);
14     cout << pv << endl;
15     // pv now points to an object of type B
16 }
复制代码
复制代码

运行结果如下:

可见dynamic_cast认为空指针的转换安全的,但这里类A和类B必须是多态的,包含虚函数,若不是,则会编译报错。

 

reinterpret_cast <new_type> (expression) 重解释转换

 

这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现,举个例子:

class A {};

class B {};

A * a = new A;

B * b = reinterpret_cast<B*>(a);//correct!

更厉害的是,reinterpret_cast可以把整型数转换成地址(指针),这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。

它同样要求new_type是指针或引用,下面的例子是通不过编译的:

double a=2000.3;

short b;

b = reinterpret_cast<short> (a); //compile error!

 

const_cast <new_type> (expression) 常量向非常量转换

这个转换好理解,可以将常量转成非常量。

复制代码
复制代码
 1 // const_cast
 2 #include <iostream>
 3 using namespace std;
 4 
 5 void print (char * str)
 6 {
 7   cout << str << endl;
 8 }
 9 
10 int main () {
11   const char * c = "sample text";
12   char *cc = const_cast<char *> (c) ;
13   Print(cc);
14   return 0;
15 }
复制代码
复制代码

char *cc = const_cast<char *>(c)可以看出了这个转换的作用了,但切记,这个转换并不转换原常量本身,即c还是常量,只是它返回的结果cc是非常量了

 

 

总结

C风格转换是“万能的转换”,但需要程序员把握转换的安全性,编译器无能为力;static_cast最接近于C风格转换,但在无关类指针转换时,编译器会报错,提升了安全性;dynamic_cast要求转换类型必须是指针或引用,且在下行转换时要求基类是多态的,如果发现下行转换不安全,dynamic_cast返回一个null指针,dynamic_cast总是认为void*之间的转换是安全的;reinterpret_cast可以对无关类指针进行转换,甚至可以直接将整型值转成指针,这种转换是底层的,有较强的平台依赖性,可移植性差;const_cast可以将常量转成非常量,但不会破坏原常量的const属性,只是返回一个去掉const的变量。

注:本文中大部分样例来源自C++标准网站:

http://www.cplusplus.com/doc/tutorial/typecasting/ 

以及微软的MSDN:

http://msdn.microsoft.com/en-us/library/cby9kycs

若有理解出错的地方,望不吝指正。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325574960&siteId=291194637