C++的隐式类型转换继承了C语言的基本数据类型的隐式转换,同时加入了派生类到基类的隐式转换。
隐式类型的转换主要用在赋值或者作为参数传递的时候,在兼容的类型之间的转换。
如果按照C++的思想,所有的操作都是函数(像+,=这些运算都是可以通过重载运算符来实现的),那总结起来就是一种用途:作为参数传递时:
class Base{
};
class Derived:public Base{
};
void func(const Base&)
{
}
int main()
{
Derived d;
func(d);
return 0;
}
显式类型转换又叫做“强制类型转换(cast)”,包含了隐式转换和隐式转换的相反过程。C++的显式类型转换和异常等一样,是C++的一个重要特性:继承了C的强制类型转换,同时加入了很多新鲜的控制。这个可以阅读BjarneStroustrup先生的C++自传《C++语言的设计和演化》一书,得到其历史渊源,里面对动态类型转换的讲解颇为详细,可以一看,在第二部分的cast一章。
在基本的数据类型之间的转换就不多说了,这里主要介绍在类的继承体系中的指针或者引用之间的类型转换,下同。
C的类型转换规则非常简单,是在编译期决定的。编译器在编译时,获取基类和派生类之间的距离,然后就是通过加上或者减去这个距离值而得到新的指针。如果是没有任何关系的两个指针值之间的转换,则不作任何处理,地址相同。
#include
using namespace std;
class Base1{
int a;
};
class Base2{
int b;
};
class Derived: public Base1, public Base2{
};
int main()
{
Derived *pd = new Derived();
Base2 *pb = new Base2();
Base2 *pb1 = (Base2 *)pd;
cout << "Derived: " << hex << pd << endl;
cout << "Derived to Base2: " << pb1 << endl;
Derived *pd1 = (Derived*)pb;
cout << "Base: " << pb << endl;
cout << "Base to Derived: " << pd1 << endl;
int a;
cout << "a : " << &a << endl;
pb = (Base2 *)&a;
cout << "Int to Base2: " << pb << endl;
return 0;
}
运行结果如下:
Derived: 0x9f25008
Derived to Base2: 0x9f2500c
Base: 0x9f25018
Base to Derived: 0x9f25014
a : 0xbfb7f79c
Int to Base2: 0xbfb7f79c
从这个结果可以看出,这种
C方式的类型转换非常依赖于程序员的的嗅觉,哈哈。完全需要程序员对错误的转换杜绝,但是这种错误的转换又是没有一个判断依据的,所以,我们不能依赖于它来进行。
这里提一下,在正常的继承关系中的指针类型转换还有一种方式无法通过这种C类型的强制转换来实现,我们在下面的static_cast中会进行介绍。
static_cast的引入,个人感觉就是为了替换C的显式类型转换。编译器对他们的处理方式非常类似,就是在编译期间获取类型信息,然后在派生类和基类的指针之间进行移动,获取最后的指针值。
和C强制类型转换方式的不同之处仅在于:当在两种没有继承关系的指针之间进行转换时,static_cast会在编译期间给出错误,禁止这样的转化(因为确实不知道如何进行转化,这样的脑残方式只是玩的时候会用到);但是C强制方式,则是对原来的指针原封不动的进行转化。
例如下面:
#include
using namespace std;
class Base{
};
int main()
{
int a;
cout << "a : " << &a << endl;
Base *pb = static_cast (&a);
cout << "Int to Base2: " << pb << endl;
return 0;
}
编译器会给出错误信息:从类型‘int*’到类型‘Base*’中的static_cast无效
当然,void类型应该作为一个特例,任何类型都是可以和void*类型之间进行互相转换并且不会影响原来的指针值的,因为void*只是作为一种存储指针的方式,对于他没有任何操作,所以不影响安全。
上面提到过,有一种继承关系中的指针转换C强制的方式无法实现,这里的static_cast方式也无法实现,那就是从虚基类的指针转换成派生类的指针。
#include
using namespace std;
class Base{
public:
int base;
virtual void f(){}
};
class Base1:public virtual Base{
public:
int base1;
virtual void f1(){}
};
class Base2:public virtual Base{
public:
int base2;
virtual void f2(){}
};
class Derived:public Base1, public Base2{
public:
int deride;
};
int main()
{
Derived *pd = new Derived();
cout << "object addr: " << hex << pd << endl;
Base *pb = static_cast (pd);
cout << "static_cast to Base: " << pb << endl;
Base2 *pb1 = static_cast(pb);
cout << "static_cast from Base to Base1: " << pd << endl;
return 0;
}
编译器给出的错误提示是:无法从基类‘
Base’转换到派生类‘
Base2’,通过虚基类‘
Base’
为什么会出现这种情况?难道是因为编译器无法得知虚基类和派生类之间的offset?确实是如此的,在Derived中Base和Base1的差距值可能是12,但是如果有另一个Derived1:public Base2,publicBase1时,offset又会发生改变。所以,编译器无法简单的根据类的定义信息得到虚基类和派生类之间的offset,因此,此种转换会失效。下面会讲到,这种问题的解决方式就是通过dynamic_cast。
对static_cast进行一下总结:
1.用于什么场合?
① 继承体系中指针之间的转换(虚基类除外)
② 基本类型之间的转换,同C转换
③ 各种类型指针和void*之间的相互转换
2.安全性?
另外,很多人对static_cast的总结是:用在“上行转换”(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。这里个人感觉,static_cast没有资格说是安全的,如下面这个例子。
#include
using namespace std;
class Base1{
int a;
};
class Base2{
int b;
};
class Derived: public Base1, public Base2{
};
int main()
{
int a;
void *p;
Derived *pd;
cout << "a : " << hex << &a << endl;
p = static_cast(&a);
cout << "int to void: " << p << endl;
pd = static_cast(p);
cout << "Derived: " << pd << endl;
Base2 *pb1 = static_cast(pd);
cout << "Derived to Base2: " << pb1 << endl;
return 0;
}
结果如下:
a : 0xbff70490
int to void: 0xbff70490
Derived: 0xbff70490
Derived to Base2: 0xbff70494
如果使用static_cast,那么使用者就必须自己清醒的记住原始的指针类型,然后在这个类型的继承体系内进行变化。否则,就等着段错误吧,哈哈。
dynamic_cast的出现是为了一个目的:安全的实现“下行转换”(基类指针转变成子类指针),不论基类是否是虚基类与否。
用法:dynamic_cast< type-id > ( expression )
说明:t
ype-id必须是类的指针、类的引用或者void*;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
同时有一个要求,expression的指针或者引用代表类必须是一个带有虚函数的类。因为dynamic_cast是在对象的内存模型中保存了offset值来实现转换的,这些offset值是保存在虚表(vtbl)中的(随编译器的方式不同而稍有差异,vs应该是存放在虚基表(vbtbl)中)。
如下的继承关系:
一种可能的内存布局就是:
所以,从上图可以看出,如果是dynamic_cast
最重要的一点,为什么称它是安全的?这需要从它的返回值进行讨论,如果符合继承体系且原始指针是指向派生类的对象的,那么返回值将是一个正确的指针值,否则会返回NULL。所以,我们可以对返回值进行判断来进行断定到底转换是否正确,从而保证程序的健壮性。
下面的例子演示了如何使用dynamic_cast
#include
using namespace std;
class Base{
public:
int base;
virtual void f(){}
};
class Base1:public virtual Base{
public:
int base1;
virtual void f1(){}
};
class Base2:public virtual Base{
public:
int base2;
virtual void f2(){}
};
class Derived:public Base1, public Base2{
public:
int deride;
virtual void f(){}
};
int main()
{
int a;
Derived *pd = new Derived();
cout << "object addr: " << hex << pd << endl;
Base2 *pb2 = dynamic_cast(pd);
cout << "dynamic_cast to Base2: " << pb2 << endl;
Base *pb = dynamic_cast (pd);
cout << "dynamic_cast to Base: " << pb << endl;
pb = dynamic_cast (pb2);
cout << "dynamic_cast pb2 to Base: " << pb << endl;
pd = dynamic_cast(pb);
cout << "dynamic_cast from Base to Derived: " << pd << endl;
cout << "a : " << &a << endl;
pd = (Derived *)&a;
cout << "c cast from int to derived: " << pd << endl;
pb2 = dynamic_cast(pd);
cout << "dynamic_cast to Base: " << pb2 << endl;
pb = new Base();
cout << "Base: " << pb << endl;
pd = dynamic_cast(pb);
cout << "dynamic_cast from Base to Derived: " << pd << endl;
pb = (Base *)&a;
cout << "c cast from int to Base: " << pb << endl;
pd = dynamic_cast(pb);
cout << "dynamic_cast from Base to Derived: " << pd << endl;
return 0;
}
结果如下:
object addr: 0x8e15008
dynamic_cast to Base2: 0x8e15010
dynamic_cast to Base: 0x8e1501c
dynamic_cast pb2 to Base: 0x8e1501c
dynamic_cast from Base to Derived: 0x8e15008
a : 0xbfae1f80
c cast from int to derived: 0xbfae1f80
dynamic_cast to Base: 0xbfae1f88
Base: 0x8e15028
dynamic_cast from Base to Derived: 0
c cast from int to Base: 0xbfae1f80
段错误 (核心已转储)
首先,从效率上来说static_cast高于dynamic_cast;
其次,如果是“上行转换”,优先采用static_cast。因为大家都是通过位移的方式来实现的,都是不安全的。
然后,如果是“下行转换”,如果涉及虚基类到派生类的转换,或者需要对返回的值进行判断,那么才考虑dynamic_cast。使用dynamic_cast还是需要对返回值进行判断;使用static_cast需要使用者对原始指针的类型记在心中。
三种转换方式的区别:
这两种用法我几乎都没用过,给自己一个提示:const_cast可以去除const和volatile属性,reinpreter_cast可以在两种没有关系的类型之间(例如int和pointer)之间转换,两次转换回来,原来的值会保持不变。
参考:http://www.cnblogs.com/chio/archive/2007/07/18/822389.html