C++ 引入了 const_cast, reinterpret_cast 之类的新的显式类型转换方式,不仅大多数 C 程序员觉得不是很习惯,就连某些有经验的C++ 程序员都会在一些细节上犯错。诚然,既然我们可以简单的写出:
int i = (int)p;// p is a pointer
这样的显式转换,为什么还要使用
int i = reinterpret_cast( p );
这么复杂的形式呢?这篇文章的目的是简单介绍 C++ 的类型转换系统,并对使用和扩展进行一些讨论
1. 为什么需要类型转换?
类型转换被用来把一个类型的值转换成另一个类型。类似于 C++ 这样的编程语言是强类型的,因此每一个值都有它相应的类型。当你需要把一个值转换为另一个类型时,你需要使用下列方式中的一种:隐式转换,显式转换和无法转换。假设我们使用老式的显式转换:
char c = 'a';
int* p = NULL;
int a = c;// 隐式转换
a=(int) p; // 显式转换
double d=(double) p;// 无法转换
通常,隐式转换意味着编译器认为你的转换是合理的或者是安全的;显式转换意味着编译器能够找到一个转换方式,但是它不保证这个转换是否安全,所以需要程序员额外指出;而无法转换则意味着编译器无法发现一条直接的路径来进行类型转换。
2. 为什么需要 C++ 风格的显式转换?
C++ 风格的显式转换为我们提供了更精确的语义和对其进一步扩展的可能。在 C 语言中,我们可以用一个简单的 (int*) 来完成下面的转换:
const char* s = 0;
int* p = (int*) s;
这一句语句,不仅转换了类型,还把 const 也去掉了。通常如果我们看到一句游离的显式转换,我们不能立即知道作者的意图,这也为今后的错误埋下了伏笔。C++ 风格的现实转换通过区分各种转换情况来增加安全性:通过 const_cast 来取消 const、volatile 之类的修饰,通过 static_cast 来做相关类型的转换,通过 reinterpret_cast 来做低级的转换,...。有一个例子可以说明这些转换的“精确”程度:
class Interface
{
int member;
};
class Base {};
class Derived : public Interface, public Base {};
int main()
{
Base* b = (Base*)100;
Derived* d1 = reinterpret_cast( b );
Derived* d2 = static_cast( b );
}
这段代码中,两个 cast 都是合法的,但是意义不同。前者意味着“把 b 的指针的值直接赋给 d1,并且把它作为 Derived 类型解释”,后者意味着“根据相关类型信息来做转换,如果可能,对 b 指针做一些偏移”。在上面这个例子里面,d1 和 d2 是不相等的!可能由于很多书上都说:如果你要在指针之间互相转换,应该使用 reinterpret_cast,所以不少程序员使用 reinterpret_cast 来做一切指针类型转换,虽然通常他们不会得到错误,但是的确是不小的隐患。
本来我这个例子的黑体部分使用的是0,有一位网友指出,C++在进行cast的时候会对0进行特殊处理。也就是说,在上面的例子中,如果使用0,那么d1和d2是一样的。
3. 一些例子
(1) itf_cast
在 COM 中,如果我有一个指向 IHtmlDocument 的接口指针,并且我想从中获得一个 IPersistFile 的指针,我们可以用下述代码:
IPersistFile* pPersistFile;
if( FAILED( pHtmlDocument->QueryInterface(IID_IPersistFile, (LPVOID*) &pPersistFile) ) )
throw something;
这段代码很简单但是不够直接,所以我们可以实现这样一个函数:
template
T1 itf_cast(T2 v2)
{
if( v2 == 0 )
return 0;
T1 v1;
if( FAILED( v2->QueryInterface( __uuidof(*v1), (LPVOID*)&v1 ) ) )
throw bad_cast();
return v1;
}
然后我们可以把上面的语句写成
pPersistFile = itf_cast( pHtmlDocument );
这非常的直观。仔细的读者可能会发现 __uuidof 不是标准的 C++ 所定义的,而是 VC 的一个扩展。事实上,在这里你可以用 traits 很简单的实现同样的功能。
(2) stream_cast
有时候,我们经常会遇到一些自定义类型之间转换问题,譬如说 std::string 和 double,甚至 std::string 和 RECT 之类的转换。如果参与转换的两个类型都定义了输入输出运算(精确的说,源类型支持输出运算,目的类型支持输入运算),那么我们可以用以下的方式来进行转换:
template
T1 stream_cast(const T2& v2)
{
std::stringstream str;
str << v2;
T1 t1;
str >> t1;
if( !str )
throw bad_cast();
return t1;
}
这样一来,你可以用以下语句进行转换:
string s("0.5");
double d = stream_cast( s );
(3) safe_cast
有时候我们希望我们的显式转换是安全的,这里安全的定义是,这个值在两个类型中的表示都是无损的。也就是说,(T2)(T1)v1 == v1。那我们可以定义这样的显式转换:
template
T1 stream_cast(const T2& v2)
{
if( (T2)(T1) v2 != v2 )
throw bad_cast();
return (T1) v2;
}
于是,stream_cast(1000); 这样的语句就会抛出异常。