static_cast与dynamic_cast

一 C语言中存在着两种类型转换:

隐式转换和显式转换

隐式转换:不同数据类型之间赋值和运算,函数调用传递参数……编译器完成

char ch;
int i = ch;

显示转换:在类型前增加 :(Type)变量 对变量进行的转换。用户显式增加

char *pc = (char*)pb;
void *ps = (void*)pa;


二 C++
中的类型转换

  通过这两种方式,C语言中大部分的类型转换都可以顺利进行。

至于能不能进行转换,转换后的结果如何,编译器不管需要用户自己去控制。

  C++继承了C中的隐式和显式转换的方式。但这种转换并不是安全和严格的,

加上C++本身对象模型的复杂性,C++增加了四个显示转换的关键字。(C++是强类型语言)

static_castdynamic_castconst_staticreinterpret_cast


1 static_cast

(1)用于基本的数据类型转换(char,int),及指针之间的转换

复制代码
test_enum type = test_enum_1;

char a ;
int b = static_cast<int>(a);
char c = static_cast<char>(b);
type = static_cast<test_enum>(b);

char* pa = NULL;
int *pb = (int*)pa;
//int *pb = static_cast<int*>(pa); //error
//pa = static_cast<char*>(pb) //error
char *pc = (char*)pb;
//char *pc = static_cast<char*>(pb); //error

void *p = static_cast<void*>(pa);
pb = static_cast<int*>(p);
pc = static_cast<char*>(p);
复制代码


(2)类层次中基类与子类成员函数指针的转换

 

复制代码
class A
{

public:
void set(){}
};

class B:public A
{
public:
void set(){}
};

typedef void (A::*PS_MFunc)();   //指向类A的成员函数指针

PS_MFunc func = &A::set;
func = static_cast<PS_MFunc>(&B::set); //基类指向子类成员函数指针,必须进行转换
复制代码


(3)类层次结构中基类与子类指针或引用之间的转换  

   上行转换:子类指针或引用转换成基类表示——安全

  下行转换:基类指针或引用转换成子类表示——危险(没有动态类型检查)

复制代码
class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};

A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();

objA = static_cast<A&>(objB); //转换为基类引用
objA = static_cast<A>(objB);

objB = static_cast<B>(objA); //error 不能进行转换

pObjA = pObjB; //right 基类指针指向子类对象
//objB = objA; //error 子类指针指向基类对象
pObjA = static_cast<A*>(pObjB); //right 基类指针指向子类
pObjB = static_cast<B*>(pObjA); //强制转换 OK 基类到子类
//pObjC = static_cast<C*>(pObjB); //error 继承于统一类的派生指针之间转换
//pObjD = static_cast<D*>(pObjC); //error 两个无关联之间转换
复制代码


2 dynamic_cast

(1)继承关系的类指针对象或引用之间转换

        

复制代码
class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};

A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();
//objA = dynamic_cast<A>(objB); //error 非引用

objA = dynamic_cast<A&>(objB);
//objB = dynamic_cast<B&>(objA); //error A 不是多态类型不能转换 若有虚函数则可以进行转换

pObjA = dynamic_cast<A*>(pObjB);
//pObjB = dynamic_cast<B*>(pObjA); //error A 继承关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjC); //error C 兄弟关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjD); //error D 没有关系 不是多态类型不能转换
复制代码

 

 

(2)包含有虚函数之间对象指针的转换   

复制代码
class A
{
Public:
Virtual ~A(){}
};
class B:public A
{
};
class C:public A
{
};
class D
{
Public:
Virtual ~D(){}
};
pObjB = dynamic_cast<B*>(pObjA);    // worning 继承关系 父类具有虚函数 多态
pObjB = dynamic_cast<B*>(pObjD); //worning 没有关系 D是多态类型可以转换
//以上结果:pObjB == NULL 此处会发生一个运行时错误
复制代码

         也就是说除了基类指针指向子类对象,可以没有虚函数外,其它要进行dynamic_cast转换必须具有虚函数才行。

那这是为什么呢?下面继续>


(3)dynamic_cast转换的安全性

         dynamic_cast是动态转换,只有在基类指针转换为子类指针时才有意义。

(子类指针转换为基类指针本来就是可以的:基类指针指向子类对象OK)。

但是基类指针转换为子类指针,并不是每一次都有效:只有基类指针本身指向的是一个派生类的对象,

然后将此基类指针转换为对应的派生类指针才是有效的。这种情况在表面上是无法判定的。此时dynamic就发挥了作用。

情况1: static_cast转换       

复制代码
class A
{
};
class B:public A
{
public:
int m; //B 成员
};


A* pObjA = new A();
B* pObjB = NULL;
pObjB = static_cast<B*>(pObjA); //基类指针转化为子类指针 成功转换
pObjB->m = 10;   //实际中pObj所指向的对象 是A类对象
//上面会发生什么呢,在VC6.0中正常运行。。。?

//如果:

pObjB = dynamic_cast<B*>(pObjA); //error 基类A没有虚函数 不构成多态
复制代码

情况2:     dynamic_cast转换    

复制代码
class A
{
public:
virtual ~A(){} //虚函数 多态
};

class B:public A
{
public:
int m;
};

A* pObjA = new A();
B* pObjB = NULL;
pObjB = dynamic_cast<B*>(pObjA); //编译通过
//实际运行结果:pObjB == NULL // dynamic_cast保证转换无效 返回NULL
复制代码

         dynamic_cast转换不成功,则返回0。

4 虚函数对于dynamic_cast转换的作用

  为何使用dynamic_cast转换类指针时,需要虚函数呢。

Dynamic_cast转换是在运行时进行转换,运行时转换就需要知道类对象的信息(继承关系等)。

如何在运行时获取到这个信息——虚函数表。

  C++对象模型中,对象实例最前面的就是虚函数表指针,

通过这个指针可以获取到该类对象的所有虚函数,包括父类的。

因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道该类对象的父类,在转换的时候就可以用来判断对象有无继承关系。

  所以虚函数对于正确的基类指针转换为子类指针是非常重要的


转载自:http://blog.csdn.net/rsqiang/article/details/8819290

1.隐式类型转换

C++的隐式类型转换继承了C语言的基本数据类型的隐式转换,同时加入了派生类到基类的隐式转换。

隐式类型的转换主要用在赋值或者作为参数传递的时候,在兼容的类型之间的转换。

如果按照C++的思想,所有的操作都是函数(像+,=这些运算都是可以通过重载运算符来实现的),那总结起来就是一种用途:作为参数传递时:

  1. class Base{  
  2. };  
  3. class Derived:public Base{  
  4. };  
  5. void func(const Base&)  
  6. {  
  7. }  
  8. int main()  
  9. {  
  10.     Derived d;  
  11.   
  12.     func(d);  
  13.   
  14.     return 0;  
  15. }  

2.显式类型转换

显式类型转换叫做“强制类型转换(cast)包含了隐式转换和隐式转换的相反过程。C++的显式类型转换和异常等一样,是C++的一个重要特性:继承了C的强制类型转换,同时加入了很多新鲜的控制。这个可以阅读BjarneStroustrup先生的C++自传《C++语言的设计和演化》一书,得到其历史渊源,里面对动态类型转换的讲解颇为详细,可以一看,在第二部分的cast一章。

2.1 C的强制类型转换

在基本的数据类型之间的转换就不多说了,这里主要介绍在类的继承体系中的指针或者引用之间的类型转换,下同。

C的类型转换规则非常简单,是在编译期决定的。编译器在编译时,获取基类和派生类之间的距离,然后就是通过加上或者减去这个距离值而得到新的指针。如果是没有任何关系的两个指针值之间的转换,则不作任何处理,地址相同。

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base1{  
  5.     int a;  
  6. };  
  7. class Base2{  
  8.     int b;  
  9. };  
  10. class Derived: public Base1, public Base2{  
  11. };  
  12.   
  13. int main()  
  14. {  
  15.     Derived *pd = new Derived();  
  16.     Base2 *pb = new Base2();  
  17.   
  18.     Base2 *pb1 = (Base2 *)pd;  
  19.     cout << "Derived: " << hex << pd << endl;  
  20.     cout << "Derived to Base2: " << pb1 << endl;  
  21.   
  22.     Derived *pd1 = (Derived*)pb;  
  23.     cout << "Base: " << pb << endl;  
  24.     cout << "Base to Derived: " << pd1 << endl;  
  25.   
  26.     int a;  
  27.     cout << "a : " << &a << endl;  
  28.     pb = (Base2 *)&a;  
  29.     cout << "Int to Base2: " << pb << endl;  
  30.     return 0;  
  31. }  

运行结果如下:

[plain]  view plain copy print ?
  1. Derived: 0x9f25008  
  2. Derived to Base2: 0x9f2500c  
  3. Base: 0x9f25018  
  4. Base to Derived: 0x9f25014  
  5. a : 0xbfb7f79c  
  6. Int to Base2: 0xbfb7f79c  
从这个结果可以看出,这种 C 方式的类型转换非常依赖于程序员的的嗅觉,哈哈。完全需要程序员对错误的转换杜绝,但是这种错误的转换又是没有一个判断依据的,所以,我们不能依赖于它来进行。

这里提一下,在正常的继承关系中的指针类型转换还有一种方式无法通过这种C类型的强制转换来实现,我们在下面的static_cast中会进行介绍。

2.2 static_cast

static_cast的引入,个人感觉就是为了替换C的显式类型转换。编译器对他们的处理方式非常类似,就是在编译期间获取类型信息,然后在派生类和基类的指针之间进行移动,获取最后的指针值。

C强制类型转换方式的不同之处仅在于:当在两种没有继承关系的指针之间进行转换时,static_cast会在编译期间给出错误,禁止这样的转化(因为确实不知道如何进行转化,这样的脑残方式只是玩的时候会用到);但是C强制方式,则是对原来的指针原封不动的进行转化。

例如下面:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base{  
  5. };  
  6. int main()  
  7. {  
  8.     int a;  
  9.     cout << "a : " << &a << endl;  
  10.     Base *pb = static_cast<Base *>(&a);  
  11.     cout << "Int to Base2: " << pb << endl;  
  12.     return 0;  
  13. }  

编译器会给出错误信息:从类型‘int*到类型‘Base*中的static_cast无效

当然,void类型应该作为一个特例,任何类型都是可以和void*类型之间进行互相转换并且不会影响原来的指针值的,因为void*只是作为一种存储指针的方式,对于他没有任何操作,所以不影响安全。

上面提到过,有一种继承关系中的指针转换C强制的方式无法实现,这里的static_cast方式也无法实现,那就是从虚基类的指针转换成派生类的指针。

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base{  
  5.     public:  
  6.         int base;  
  7.         virtual void f(){}  
  8. };  
  9. class Base1:public virtual Base{  
  10.     public:  
  11.         int base1;  
  12.         virtual void f1(){}  
  13. };  
  14. class Base2:public virtual Base{  
  15.     public:  
  16.         int base2;  
  17.         virtual void f2(){}  
  18. };  
  19. class Derived:public Base1, public Base2{  
  20.     public:  
  21.         int deride;  
  22. };  
  23.   
  24. int main()  
  25. {  
  26.     Derived *pd = new Derived();  
  27.     cout << "object addr: " << hex << pd << endl;  
  28.   
  29.     Base *pb = static_cast<Base *>(pd);  
  30.     cout << "static_cast to Base: " << pb << endl;  
  31.   
  32.     Base2 *pb1 = static_cast<Base2*>(pb);  
  33.     cout << "static_cast from Base to Base1: " << pd << endl;  
  34.   
  35.     return 0;  
  36. }  
编译器给出的错误提示是: 无法从基类‘ Base 转换到派生类‘ Base2 ,通过虚基类‘ Base

为什么会出现这种情况?难道是因为编译器无法得知虚基类和派生类之间的offset?确实是如此的,在DerivedBaseBase1的差距值可能是12,但是如果有另一个Derived1:public Base2publicBase1时,offset又会发生改变。所以,编译器无法简单的根据类的定义信息得到虚基类和派生类之间的offset,因此,此种转换会失效。下面会讲到,这种问题的解决方式就是通过dynamic_cast

static_cast进行一下总结:

1.用于什么场合?

① 继承体系中指针之间的转换(虚基类除外)

② 基本类型之间的转换,同C转换

③ 各种类型指针和void*之间的相互转换

2.安全性?

另外,很多人对static_cast的总结是:用在“上行转换”(派生类的指针或引用转换成基类表示是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。这里个人感觉,static_cast没有资格说是安全的,如下面这个例子。

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base1{  
  5.     int a;  
  6. };  
  7. class Base2{  
  8.     int b;  
  9. };  
  10. class Derived: public Base1, public Base2{  
  11. };  
  12.   
  13. int main()  
  14. {  
  15.     int a;  
  16.     void *p;  
  17.     Derived *pd;  
  18.   
  19.     cout << "a : " << hex << &a << endl;  
  20.   
  21.     p = static_cast<void*>(&a);  
  22.     cout << "int to void: " << p << endl;  
  23.   
  24.     pd = static_cast<Derived*>(p);  
  25.     cout << "Derived: " << pd << endl;  
  26.       
  27.     Base2 *pb1 = static_cast<Base2 *>(pd);  
  28.     cout << "Derived to Base2: " << pb1 << endl;  
  29.   
  30.     return 0;  
  31. }  
结果如下:

a : 0xbff70490

[plain]  view plain copy print ?
  1. int to void: 0xbff70490  
  2. Derived: 0xbff70490  
  3. Derived to Base2: 0xbff70494  

如果使用static_cast,那么使用者就必须自己清醒的记住原始的指针类型,然后在这个类型的继承体系内进行变化。否则,就等着段错误吧,哈哈。

2.3 dynamic_cast

dynamic_cast的出现是为了一个目的:安全的实现“下行转换”(基类指针转变成子类指针),不论基类是否是虚基类与否。

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

说明:t

  1. ype-id必须是类的指针、类的引用或者void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用

  2. 同时有一个要求,expression的指针或者引用代表类必须是一个带有虚函数的类。因为dynamic_cast是在对象的内存模型中保存了offset值来实现转换的,这些offset值是保存在虚表(vtbl)中的(随编译器的方式不同而稍有差异,vs应该是存放在虚基表(vbtbl)中)。

如下的继承关系:


一种可能的内存布局就是:


所以,从上图可以看出,如果是dynamic_cast<C*>(pb),那么转换过程是找到B部分的虚表,然后将pb的指针值加上-delta(B)即可。所以,dynamic_cast在运行时消耗的时间较多。相比较,static_cast则在运行时不消耗时间,在编译期间即可完成。

  1. 最重要的一点,为什么称它是安全的?这需要从它的返回值进行讨论,如果符合继承体系且原始指针是指向派生类的对象的,那么返回值将是一个正确的指针值,否则会返回NULL。所以,我们可以对返回值进行判断来进行断定到底转换是否正确,从而保证程序的健壮性。

下面的例子演示了如何使用dynamic_cast

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Base{  
  5.     public:  
  6.         int base;  
  7.         virtual void f(){}  
  8. };  
  9. class Base1:public virtual Base{  
  10.     public:  
  11.         int base1;  
  12.         virtual void f1(){}  
  13. };  
  14. class Base2:public virtual Base{  
  15.     public:  
  16.         int base2;  
  17.         virtual void f2(){}  
  18. };  
  19. class Derived:public Base1, public Base2{  
  20.     public:  
  21.         int deride;  
  22.         virtual void f(){}  
  23. };  
  24.   
  25. int main()  
  26. {  
  27.     int a;  
  28.   
  29.     Derived *pd = new Derived();  
  30.     cout << "object addr: " << hex << pd << endl;  
  31.   
  32.     Base2 *pb2 = dynamic_cast<Base2 *>(pd);  
  33.     cout << "dynamic_cast to Base2: " << pb2 << endl;  
  34.   
  35.     Base *pb = dynamic_cast<Base *>(pd);  
  36.     cout << "dynamic_cast to Base: " << pb << endl;  
  37.   
  38.     pb = dynamic_cast<Base*>(pb2);  
  39.     cout << "dynamic_cast pb2 to Base: " << pb << endl;  
  40.   
  41.     pd = dynamic_cast<Derived*>(pb);  
  42.     cout << "dynamic_cast from Base to Derived: "  << pd << endl;  
  43.   
  44.     cout << "a : " << &a << endl;  
  45.   
  46.     pd = (Derived *)&a;  
  47.     cout << "c cast from int to derived: " << pd << endl;  
  48.     pb2 = dynamic_cast<Base2*>(pd);  
  49.     cout << "dynamic_cast to Base: " << pb2 << endl;  
  50.       
  51.     pb = new Base();  
  52.     cout << "Base: " << pb << endl;  
  53.     pd = dynamic_cast<Derived*>(pb);  
  54.     cout << "dynamic_cast from Base to Derived: "  << pd << endl;  
  55.   
  56.     pb = (Base *)&a;  
  57.     cout << "c cast from int to Base: " << pb << endl;  
  58.     pd = dynamic_cast<Derived*>(pb);  
  59.     cout << "dynamic_cast from Base to Derived: "  << pd << endl;  
  60.     return 0;  
  61. }  
结果如下:

[plain]  view plain copy print ?
  1. object addr: 0x8e15008  
  2. dynamic_cast to Base2: 0x8e15010  
  3. dynamic_cast to Base: 0x8e1501c  
  4. dynamic_cast pb2 to Base: 0x8e1501c  
  5. dynamic_cast from Base to Derived: 0x8e15008  
  6. a : 0xbfae1f80  
  7. c cast from int to derived: 0xbfae1f80  
  8. dynamic_cast to Base: 0xbfae1f88  
  9. Base: 0x8e15028  
  10. dynamic_cast from Base to Derived: 0  
  11. c cast from int to Base: 0xbfae1f80  
  12. 段错误 (核心已转储)  

结果表明“上行转换”中依然是“不安全的”,只是通过对指针偏移来进行。“下行转换”确实是安全的。其中有一个段错误,希望看客可以理解下,我可以提醒下, 这个段错误是在dynamic_cast中抛出的。

2.4 static_castdynamic_cast的选择

首先,从效率上来说static_cast高于dynamic_cast

其次,如果是“上行转换”,优先采用static_cast。因为大家都是通过位移的方式来实现的,都是不安全的。

然后,如果是“下行转换”,如果涉及虚基类到派生类的转换,或者需要对返回的值进行判断,那么才考虑dynamic_cast。使用dynamic_cast还是需要对返回值进行判断;使用static_cast需要使用者对原始指针的类型记在心中。

三种转换方式的区别:


2.5 const_castreinpreter_cast

这两种用法我几乎都没用过,给自己一个提示:const_cast可以去除constvolatile属性,reinpreter_cast可以在两种没有关系的类型之间(例如intpointer)之间转换,两次转换回来,原来的值会保持不变。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值