您的位置:首页 > 编程语言 > C语言/C++

深入理解C++标准类型转换符:static_cast、dynamic_cast、reinterpret_cast和const_cast

2013-01-23 15:10 701 查看
注:文章内容主要来自于MSDN以及Jerry19880126的博客

对标准类型转换符最容易理解的解释:

static_cast-- 一般的转换(no run-time check)。通常如果你不知道该用哪个,就用这个。

dynamic_cast-- 通常在基类和派生类之间转换时使用

const_cast --主要针对const和volatile的转换

reinterpret_cast-- 用于进行没有任何关联之间的转换,比如一个字符指针转换为一个整形数。

相关概念解释:

上行转换(up-casting)--the up direction (toward the root Object class)。把子类的指针或引用转换成基类表示

下行转换(down-casting)--the down direction, along the inheritance path.。把基类指针或引用转换成子类表示

类型转换不安全性来源于两个方面:
其一是类型的窄化转化,会导致数据位数的丢失;比如int类型转short。float类型转int.

其二是在类继承链中,将父类对象的地址(指针)强制转化成子类的地址(指针)。

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

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

标准类型转换符详细介绍

1. static_cast

用法:static_cast < type-id> ( expression )

编译器在编译期处理

将地址expression转换成类型type-id,type-id和expression必须是指针、引用、算术类型或枚举类型。四种转换类型中唯一一个可以用于非指针、非引用类型的转换。

表达式static_cast<type-id>(expression), expression的值转换为模板中指定的类型type-id。在运行时转换过程中,不进行类型检查来确保转换的安全性。static_cast它能在内置的数据类型间互相转换,对于类只能在有联系的指针类型间进行转换。可以在继承体系中把指针转换来、转换去,但是不能转换成继承体系外的一种类型。

该运算符主要有如下几种用法:

① 用于类层次结构中基类和子类之间指针或引用的转换。 进行上行转换是安全的; 进行下行转换时,由于没有动态类型检查,所以是不安全的。

② 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③ 把空指针转换成目标类型的空指针。

④ 把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

转换安全性示例:

class A { ... };
class B { ... };
class D : public B { ... };
void f(B* pb, D* pd)
{
D* pd2 = static_cast<D*>(pb);        // 不安全, pb可能只是B的指针
B* pb2 = static_cast<B*>(pd);        // 安全的
A* pa2 = static_cast<A*>(pb);        // 错误A与B没有继承关系
...
}


对于不相关类指针之间的转换。参见下面的例子:

// class type-casting
#include <iostream>
using namespace std;

class CDummy {
float i,j;
};

class CAddition {
int x,y;
public:
CAddition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};

int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return 0;
}
CAddition与CDummy类没有任何关系了,但main()中C风格的转换仍是允许的padd = (CAddition*) &d,这样的转换没有安全性可言。

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

int main () {
CDummy d;
CAddition * padd;
padd = static_cast<CAddition*> (&d);
cout << padd->result();
return 0;
}


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



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

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

2. dynamic_cast

用法:dynamic_cast < type-id> ( expression )

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;它有三个重要的约束条件,

第一、必须用于类与子类之间的转换;

第二、必须用于指针或引用类型的转换;

第三、下行转换时要求基类必须有虚函数(基类中包含至少一个虚函数)。

如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

在运行期,会检查这个转换是否可能。type-id必须是一个指针、引用或无类型的指针。expression必须是决定一个指针或引用的表达式。dynamic_cast 仅能应用于指针或者引用,不支持内置数据类型。表达式dynamic_cast<type-id>(expression) 将expression值转换为类型为type-id的对象指针。如果类型type-id不是expression的某个基类型,该操作将返回一个空指针。它不仅仅像static_cast那样,检查转换前后的两个指针是否属于同一个继承树,它还要检查被指针引用的对象的实际类型,确定转换是否可行。如果可以,它返回一个新指针,甚至计算出为处理多继承的需要的必要的偏移量。如果这两个指针间不能转换,转换就会失败,此时返回空指针(NULL)。

很明显,为了让dynamic_cast能正常工作,必须让编译器支持运行期类型信息(RTTI)。

#include <iostream>
using namespace std;
class CBase { };
class CDerived: public CBase { };

int main()
{
CBase b; CBase* pb;
CDerived d; CDerived* pd;

pb = dynamic_cast<CBase*>(&d);     // ok: derived-to-base
pd = dynamic_cast<CDerived*>(&b);  // wrong: base-to-derived
}
在最后一行代码有问题,编译器给的错误提示如下图所示:



把类的定义改成:

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(空指针)。

再举一个例子:

#include <iostream>
#include <exception>
using namespace std;

class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };

int main () {
try {
CBase * pba = new CDerived;
CBase * pbb = new CBase;
CDerived * pd;

pd = dynamic_cast<CDerived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast" << endl;

pd = dynamic_cast<CDerived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast" << endl;

}
catch (exception& e) {
cout << "Exception: " << e.what();
}
return 0;
}

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

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

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

#include <iostream>
using namespace std;
class A {virtual void f(){}};
class B {virtual void f(){}};

int main() {
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa);
cout << pv << endl;
// pv now points to an object of type A

pv = dynamic_cast<void*>(pb);
cout << pv << endl;
// pv now points to an object of type B
}


运行结果如下:



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

3. const_cast<type-id>(expression)

编译器在编译期处理

去掉类型中的常量,除了const 或不稳定的变址数,type-id和expression必须是相同的类型。

表达式const_cast<type-id>(expression)被用于从一个类中去除以下这些属性:const, volatile, 和 __unaligned。

class A { ... };
void f()
{
const A *pa = new A;     // const对象
A *pb;                   // 非const对象
pb = pa;                 // 出错,不能将const对象指针赋值给非const对象
pb = const_cast<A*>(pa); // 现在OK了
...
}

对于本身定义时为const的类型,即使你去掉const性,在你操作这片内容时候也要小心,只能r不能w操作,否则还是会出错:

const char* p = "123";
char* c = const_cast<char*>(p);
c[0] = 1;   //表面上通过编译去掉了const性,但是操作其地址时系统依然不允许这么做。


const_cast操作不能在不同的种类间转换。相反,它仅仅把一个它作用的表达式转换成常量。它可以使一个本来不是const类型的数据转换成const类型的,或者把const属性去掉。尽量不要使用const_cast,如果发现调用自己的函数,竟然使用了const_cast,那就赶紧打住,重新考虑一下设计吧。

去除“指针”或“引用”的const、volatile、_unaligned性。很多人都认为用这个运算符就可以让const这么“固若金汤”的东西“形同虚设”,实则不然。本质上它能做到也就是仅仅为你剥去一层虚假的外壳。如果“被指向”或“被引用”的东西本身就是const的,那么任凭你费多大力气都是徒劳的。一般它都是在这样的一套逻辑中:

int a=34;
const int * pcint=&a;
int *pint=const_cast<int*>(pcint);
*pint=0;

这样就修改了本来是const的指针(该指针要求不能修改它指向的东东),这里如果改为const int a=34;那么虽然编译依然能通过,运行依然OK,但是实际上当你用*pint来修改时,你会得到修改后的值为:a依然为34;而*pint确实为0了,但有趣的是此时pint=&a依然成立,虽然pint指向a,但是取出它的值却不等于a了。至此,希望大家今后不要再误用const_cast了。

4. reinterpret_cast<type-id>(expression)--重解释转换

编译器在编译期处理

任何指针都可以转换成其它类型的指针,type-id必须是一个指针、引用、算术类型、指向函数的指针或指向一个类成员的指针。

表达式reinterpret_cast<type-id>(a)能够用于诸如char* 到 int*,或者One_class* 到 Unrelated_class*等类似这样的转换,因此可能是不安全的。用于对指针的重新包装,也就是指针类别之间的转化。除此之外还可以用于指针类型与unsigned int类型之间的转化。

class A { ... };
class B { ... };
void f()
{
A* pa = new A;
void* pv = reinterpret_cast<B*>(pa);
// pv 现在指向了一个类型为B的对象,这可能是不安全的
...
}


使用reinterpret_cast 的场合不多,仅在非常必要的情形下,其他类型的强制转换不能满足要求时才使用。

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

class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);// 正确

更厉害的是,reinterpret_cast可以把整型数转换成地址(指针),这种转换在系统底层的操作,有极强的平台依赖性,移植性差。它同样要求new_type是指针或引用,下面的例子是通不过编译的。

double a = 2000.3;
short b;
b = reinterpret_cast<short> (a); // 编译错误

类型转换符相互比较

1. static_cast vs reinterpret_cast

reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的(这句话是C++编程思想中的原话)。

static_cast 和 reinterpret_cast 操作符修改了操作数类型,它们不是互逆的。

static_cast 在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的。

另一方面,reinterpret_cast是C++里的强制类型转换符,操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。

例子如下:

int n=9;
double d=static_cast < double > (n);


上面的例子中,我们将一个变量从 int 转换到 double。这些类型的二进制表达式是不同的。 要将整数 9 转换到双精度整数9,static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0。

而reinterpret_cast 的行为却不同:

int n=9;
double d=reinterpret_cast<double & > (n);


这次, 结果有所不同。在进行计算以后, d 包含无用值。这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d,没有进行必要的分析。因此,你需要谨慎使reinterpret_cast。

reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。

例如,假设你有一个函数指针数组:

typedefvoid(*FuncPtr)();// FuncPtr is一个指向函数的指针,该函数没有参数,返回值类型为void
FuncPtrfuncPtrArray[10];// funcPtrArray是一个能容纳10个FuncPtrs指针的数组


你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。

funcPtrArray[0] = &doSomething;// 错误!类型不匹配

reinterpret_cast可以让你迫使编译器以你的方法去看待它们:

funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);

转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果。

2. dynamic_cast vs static_cast

class B { ... };

class D : public B { ... };

void f(B* pb)

{

D* pd1 = dynamic_cast<D*>(pb);

D* pd2 = static_cast<D*>(pb);

}

如果pb确实是指向D类型对象,则pd1和pd2将拥有同样的值。即使pd为0,它们的值也相同。如果pb执行B类型对象而不是D类型,则dynamic_cast返回0.可是static_cast依赖编程者安全断言--pb指向D类型对象并简单地返回给定的D对象指针。

dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类指针,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性。static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换、窄化变换(这种变换会导致对象切片,丢失信息)、用VOID*的强制变换、隐式类型变换等.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐