reinterpret_cast <new_type> (expression)
reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
什么是无关类型?我没有弄清楚,没有找到好的文档来说明类型之间到底都有些什么关系(除了类的继承以外)。后半句倒是看出了reinterpret_cast的字面意思:重新解释(类型的比特位)。我们真的可以随意将一个类型值的比特位交给另一个类型作为它的值吗?其实不然。
IBM的C++指南里倒是明确告诉了我们reinterpret_cast可以,或者说应该在什么地方用来作为转换运算符:
- 从指针类型到一个足够大的整数类型
- 从整数类型或者枚举类型到指针类型
- 从一个指向函数的指针到另一个不同类型的指向函数的指针
- 从一个指向对象的指针到另一个不同类型的指向对象的指针
- 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
- 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
不过我在Xcode中测试了一下,事实上reinterpret_cast的使用并不局限在上边所说的几项的,任何类型的指针之间都可以互相转换,都不会得到编译错误。上述列出的几项,可能 是Linux下reinterpret_cast使用的限制,也可能是IBM推荐我们使用reinterpret_cast的方式
所以总结来说:reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
(所谓"足够大的整数类型",取决于操作系统的参数,如果是32位的操作系统,就需要整形(int)以上的;如果是64位的操作系统,则至少需要长整形(long)。具体大小可以通过sizeof运算符来查看)。
reinterpret_cast有何作用
从上边对reinterpret_cast介绍,可以感觉出reinterpret_cast是个很强大的运算符,因为它可以无视种族隔离,随便搞。但就像生物的准则,不符合自然规律的随意杂交只会得到不能长久生存的物种。随意在不同类型之间使用reinterpret_cast,也之后造成程序的破坏和不能使用。
比如下边的代码
typedef int (*FunctionPointer)(int);
int value = 21;
FunctionPointer funcP;
funcP = reinterpret_cast<FunctionPointer> (&value);
funcP(value);
我先用typedef定义了一个指向函数的指针类型,所指向的函数接受一个int类型作为参数。然后我用reinterpret_cast将一个整型的地址转换成该函数类型并赋值给了相应的变量。最后,我还用该整形变量作为参数交给了指向函数的指针变量。
这个过程编译器都成功的编译通过,不过一旦运行我们就会得到"EXC_BAD_ACCESS"的运行错误,因为我们通过funcP所指的地址找到的并不是函数入口。
由此可知,reinterpret_cast虽然看似强大,作用却没有那么广。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。
这样说起来,reinterpret_cast转换成其它类型的目的只是临时的隐藏自己的什么(做个卧底?),要真想使用那个值,还是需要让其露出真面目才行。那到底它在C++中有其何存在的价值呢?
MSDN的Visual C++ Developer Center 给出了它的使用价值:用来辅助哈希函数。下边是MSNDN上的例子:
// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}
using namespace std;
int main() {
int a[20];
for ( int i = 0; i < 20; i++ )
cout << Hash( a + i ) << endl;
}
//如果跟我一样是64位的系统,可能需要将unsigned int改成 unsigned long才能运行。
这段代码适合体现哈希的思想,暂时不做深究,但至少看Hash函数里面的操作,也能体会到,对整数的操作显然要对地址操作更方便。在集合中存放整形数值,也要比存放地址更具有扩展性(当然如果存void *扩展性也是一样很高的),唯一损失的可能就是存取的时候整形和地址的转换(这完全可以忽略不计)。
不过可读性可能就不高,所以在这种情况下使用的时候,就可以用typedef来定义个指针类型:
typedef unsigned int PointerType;
这样不是更棒,当我们在64位机器上运行的时候,只要改成:
typedef unsigned long PointerType;
当reinterpret_cast面对const
IBM的C++指南指出:reinterpret_cast不能像const_cast那样去除const修饰符。这是什么意思呢?代码还是最直观的表述:
int main()
{
typedef void (*FunctionPointer)(int);
int value = 21;
const int* pointer = &value;
//int * pointer_r = reinterpret_cast<int*> (pointer);
// Error: reinterpret_cast from type 'const int*' to type 'int*' casts away constness
FunctionPointer funcP = reinterpret_cast<FunctionPointer> (pointer);
}
例子里,我们像前面const_cast一篇举到的例子那样,希望将指向const的指针用运算符转换成非指向const的指针。但是当实用reinterpret_cast的时候,编译器直接报错组织了该过程。这就体现出了const_cast的独特之处。
但是,例子中还有一个转换是将指向const int的指针付给指向函数的指针,编译顺利通过编译,当然结果也会跟前面的例子一样是无意义的。
如果我们换一种角度来看,这似乎也是合理的。因为
const int* p = &value;
int * const q = &value;
这两个语句的含义是不同的,前者是"所指内容不可变",后者则是"指向的地址不可变"(具体参考此处)。因此指向函数的指针默认应该就带有"所指内容不可变"的特性。
毕竟函数在编译之后,其操作过程就固定在那里了,我们唯一能做的就是传递一些参数给指针,而无法改变已编译函数的过程。所以从这个角度来想,上边例子使用reinterpret_cast从const int * 到FunctionPointer转换就变得合理了,因为它并没有去除const限定
static_cast <new_type> (expression)
虽然const_cast是用来去除变量的const限定,但是static_cast却不是用来去除变量的static引用。其实这是很容易理解的,static决定的是一个变量的作用域和生命周期,比如:在一个文件中将变量定义为static,则说明这个变量只能在本Package中使用;在方法中定义一个static变量,该变量在程序开始存在直到程序结束;类中定义一个static成员,该成员随类的第一个对象出现时出现,并且可以被该类的所有对象所使用。
对static限定的改变必然会造成范围性的影响,而const限定的只是变量或对象自身。但无论是哪一个限定,它们都是在变量一出生(完成编译的时候)就决定了变量的特性,所以实际上都是不容许改变的。这点在const_cast那部分就已经有体现出来。
static_cast和reinterpret_cast一样,在面对const的时候都无能为力:两者都不能去除const限定。两者也存在的很多的不同,比如static_cast不仅可以用在指针和引用上,还可以用在基础数据和对象上;前面提到过reinterpret_cast可以用在"没有关系"的类型之间,而用static_cast来处理的转换就需要两者具有"一定的关系"了。
还是用例子来说明比较直观一些。
还是用例子来说明比较直观一些。
在reinterpret_cast一篇,已经提到过reinterpret_cast可以在任意指针之间进行互相转换,即使这些指针所指的内容是毫无关系的,也就是说一下语句,编译器是不会报错的,但是对于程序来说也是毫无意义可言的,只会造成程序崩溃:
#include <iostream>
using namespace std;
unsigned short Hash( void *p ) {
unsigned long val = reinterpret_cast<unsigned long>( p );
return ( unsigned short )( val ^ (val >> 16));
}
class Something
{
/* Some codes here */
};
class Otherthing
{
/* Some codes here */
};
int main() {
typedef unsigned short (*FuncPointer)( void *) ;
FuncPointer fp = Hash; //right, this is what we want
int a[10];
const int* ch = a; //right, array is just like pointer
char chArray[4] = {'a','b','c','d'};
fp = reinterpret_cast<FuncPointer> (ch); //no error, but does not make sense
ch = reinterpret_cast<int*> (chArray); //no error
cout <<hex<< *ch; //output: 64636261 //it really reinterpret the pointer
Something * st = new Something();
Otherthing * ot = reinterpret_cast<Otherthing*> (st); //cast between objects with on relationship
}
而以上转换,都是static_cast所不能完成的任务,也就是说把上边程序里所有的reinterpret_cast换成static_cast的话,就会立即得到编译错误,因为目标指针和原始指针之间不存在"关系"
从上边的程序,也就一下子看出来了reinterpret_cast和static_cast之间最本质的区别。
而以上转换,都是static_cast所不能完成的任务,也就是说把上边程序里所有的reinterpret_cast换成static_cast的话,就会立即得到编译错误,因为目标指针和原始指针之间不存在"关系"
从上边的程序,也就一下子看出来了reinterpret_cast和static_cast之间最本质的区别。
对于static_cast所需要的关系,"继承"绝对是其中之一,所以static_cast支持指向基类的指针和指向子类的指针之间的互相转换:
class Parents
{
public:
virtual ~Parents(){}
/*codes here*/
};
class Children : public Parents
{
/*codes here*/
};
int main()
{
Children * daughter = new Children();
Parents * mother = static_cast<Parents*> (daughter); //right, cast with polymorphism
Parents * father = new Parents();
Children * son = static_cast<Children*> (father); //no error, but not safe
}
但是从基类到子类的转换,用static_cast并不是安全的,具体的问题会在dynamic_cast一篇阐述。
在指针和引用方便,似乎也只有继承关系是可以被static_cast接受的,其他情况的指针和引用转换都会被static_cast直接扔出编译错误,而这层关系上的转换又几乎都可以被dynamic_cast所代替。这样看起来static_cast运算符的作用就太小了。
实际上static_cast真正用处并不在指针和引用上,而在基础类型和对象的转换上 。 而基于基础类型和对象的转换都是其他三个转换运算符所办不到的。
这些转换跟C++用户自定义类型转换一文中所设计的内容比较接近,所以在那边文章中出现转换可以全部加上static_cast。
基础类型转换:
float floatValue = 21.7;
int intValue = 7;
cout << floatValue / 7 << "\t\t" << static_cast<int> (floatValue)/7 <<endl;
cout << intValue/3 << "\t\t" << static_cast<double> (intValue)/3 << endl;
//Output:
//3.1 3
//2 2.33333
从输出结果可以看出转换是成功并且正确的。
对于对象的转换,也是需要又关系的,这层关系就是C++用户自定义类型转换中提到的方法:
- 构造函数(Constructor)
- 类型转换运算符(Type –Cast Operator
static_cast会根据上述顺序寻找到合适的方法进行类型转换。
赋值运算符并不被算在内,因为它自身已经是一种运算符,不能再当做转换运算符来用。
int main(void)
{
Ape a;
Human h = static_cast<Human> (a); // using promtion constructor
Programmer p;
p = static_cast<Programmer> (h); // using Programmer-cast operaotor
//Ape a2;
//a2 = static_cast<Ape> (p); //Error, assignment operator should be used directly
return 0;
}
(类的代码见C++用户自定义类型转换,或者下载代码查看)
传统转换方式实现static_cast运算符
从上边对static_cast分析可以跟看,static_cast跟传统转换方式几乎是一致的,所以只要将static_cast和圆括号去掉,再将尖括号改成圆括号就变成了传统的显示转换方式。在C++用户自定义类型转换一文已有很多的介绍了。
dynamic_cast <new_type> (expression)
dynamic_cast运算符,应该算是四个里面最特殊的一个,因为它涉及到编译器的属性设置,而且牵扯到的面向对象的多态性跟程序运行时的状态也有关系,所以不能完全的使用传统的转换方式来替代。但是也因此它是最常用,最不可缺少的一个运算符。
与static_cast一样,dynamic_cast的转换也需要目标类型和源对象有一定的关系:继承关系。 更准确的说,dynamic_cast是用来检查两者是否有继承关系。因此该运算符实际上只接受基于类对象的指针和引用的类转换。从这个方面来看,似乎dynamic_cast又和reinterpret_cast是一致的,但实际上,它们还是存在着很大的差别。
还是用代码来解释,让编译器来说明吧。
/
// cast_operator_comparison.cpp
// Language: C++
// Complier: Visual Studio 2010, Xcode3.2.6
// Platform: MacBook Pro 2010
// Application: none
// Author: Ider, Syracuse University ider.cs@gmail.com
///
#include <string>
#include <iostream>
using namespace std;
class Parents
{
public:
Parents(string n="Parent"){ name = n;}
virtual ~Parents(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my children." << endl;
}
void Work()
{
cout << "\tI am " << name <<", I need to work for my family." << endl;;
}
protected:
string name;
};
class Children : public Parents
{
public:
Children(string n="Child"):Parents(n){ }
virtual ~Children(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my parents." << endl;
}
/*
**Children inherit Work() method from parents,
**it could be treated like part-time job.
*/
void Study()
{
cout << "\tI am " << name << ", I need to study for future." << endl;;
}
private:
//string name; //Inherit "name" member from Parents
};
class Stranger
{
public:
Stranger(string n="stranger"){name = n;}
virtual ~Stranger(){}
void Self_Introduce()
{
cout << "\tI am a stranger" << endl;
}
void Speak()
{
//cout << "I am a stranger" << endl;
cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;
}
private:
string name;
};
int main() {
/******* cast from child class to base class *******/
cout << "dynamic_cast from child class to base class:" << endl;
Children * daughter_d = new Children("Daughter who pretend to be my mother");
Parents * mother_d = dynamic_cast<Parents*> (daughter_d); //right, cast with polymorphism
mother_d->Speak();
mother_d->Work();
//mother_d->Study(); //Error, no such method
cout << "static_cast from child class to base class:" << endl;
Children * son_s = new Children("Son who pretend to be my father");
Parents * father_s = static_cast<Parents*> (son_s); //right, cast with polymorphism
father_s->Speak();
father_s->Work();
//father_s->Study(); //Error, no such method
cout << endl;
/******* cast from base class to child class *******/
cout << "dynamic_cast from base class to child class:" << endl;
Parents * father_d = new Parents("Father who pretend to be a my son");
Children * son_d = dynamic_cast<Children*> (father_d); //no error, but not safe
if (son_d)
{
son_d->Speak();
son_d->Study();
}
else cout << "\t[null]" << endl;
cout << "static_cast from base class to child class:" << endl;
Parents * mother_s = new Parents("Mother who pretend to be a my daugher");
Children * daughter_s = static_cast<Children*> (mother_s); //no error, but not safe
if (daughter_s)
{
daughter_s->Speak();
daughter_s->Study();
}
else cout << "\t[null]" << endl;
cout << endl;
/******* cast between non-related class *******/
cout << "dynamic_cast to non-related class:" << endl;
Stranger* stranger_d = dynamic_cast<Stranger*> (daughter_d);
if (stranger_d)
{
stranger_d->Self_Introduce();
stranger_d->Speak();
}
else cout <<"\t[null]"<<endl;
//Stranger* stranger_s = static_cast<Stranger*> (son_s); //Error, invalid cast
cout << "reinterpret_cast to non-related class:" << endl;
Stranger* stranger_r = reinterpret_cast<Stranger*> (son_s);
if (stranger_r)
{
stranger_d->Self_Introduce();
//stranger_d->Speak(); //This line would cause program crush,
//as "name" could not be found corretly.
}
else cout << "\t[null]" << endl;
cout << endl;
/******* cast back*******/
cout << "use dynamic_cast to cast back from static_cast:" << endl;
Children* child_s = dynamic_cast<Children*> (father_s);
if (child_s)
{
child_s->Speak();
child_s->Work();
}
else cout << "\t[null]" << endl;
//cout<<typeid(stranger_r).name()<<endl;
cout << "use dynamic_cast to cast back from reinterpret_cast:" << endl;
Children* child_r = dynamic_cast<Children*> (stranger_r);
if (child_r)
{
child_r->Speak();
child_r->Work();
}
else cout << "\t[null]" << endl;
delete daughter_d;
delete son_s;
delete father_d;
delete mother_s;
return 0;
}
/********************* Result *********************/
//dynamic_cast from child class to base class:
// I am Daughter who pretend to be my mother, I love my parents.
// I am Daughter who pretend to be my mother, I need to work for my family.
//static_cast from child class to base class:
// I am Son who pretend to be my father, I love my parents.
// I am Son who pretend to be my father, I need to work for my family.
//
//dynamic_cast from base class to child class:
// [null]
//static_cast from base class to child class:
// I am Mother who pretend to be a my daugher, I love my children.
// I am Mother who pretend to be a my daugher, I need to study for future.
//
//dynamic_cast to non-related class:
// [null]
//reinterpret_cast to non-related class:
// I am a stranger
//
//use dynamic_cast to cast back from static_cast:
// I am Son who pretend to be my father, I love my parents.
// I am Son who pretend to be my father, I need to work for my family.
//use dynamic_cast to cast back from reinterpret_cast:
// [null]
从上边的代码和输出结果可以看出:
对于从子类到基类的指针转换,static_cast和dynamic_cast都是成功并且正确的(所谓成功是说转换没有编译错误或者运行异常;所谓正确是指方法的调用和数据的访问输出是期望的结果),这是面向对象多态性的完美体现。
而从基类到子类的转换,static_cast和dynamic_cast都是成功的,但是正确性方面,我对两者的结果都先进行了是否非空的判别:dynamic_cast的结果显示是空指针,而static_cast则是非空指针。但很显然,static_cast的结果应该算是错误的,子类指针实际所指的是基类的对象,而基类对象并不具有子类的Study()方法(除非妈妈又想去接受个"继续教育")。
对于没有关系的两个类之间的转换,输出结果表明,dynamic_cast依然是返回一个空指针以表示转换是不成立的;static_cast直接在编译期就拒绝了这种转换。
reinterpret_cast成功进行了转换,而且返回的值并不是空指针,但是结果显然是错误的,因为Children类显然不具有Stranger的Self_Introduce()。虽然两者都具有name数据成员和Speak()方法,,Speak()方法也只是调用了该相同名称的成员而已,但是对于Speak()的调用直接造成了程序的崩溃。
其实前面static_cast的转换的结果也会跟reinterpret_cast一样造成的程序的崩溃,只是类的方法都只有一份,只有数据成员属于对象,所以在调用那些不会访问对象的数据的方法时(如Stranger的Self_Introduce())并不会造成崩溃。而daughter_s->Speak();和daughter_s->Study();调用了数据成员却没有出现运行错误,则是因为该成员是从基类继承下来的,通过地址偏移可以正确的到达数据成员所在的地址以读取出数据。
最后,程序里还用dynamic_cast希望把用其他转换运算符转换过去的指针转换回来。对于使用static_cast转换后指向了子类对象的基类指针,dynamic_cast判定转换是合理有效的,因此转换成功获得一个非空的指针并且正确输出了结果;而对于reinterpret_cast转换的类型,的确如它的功能一样——重新解析,变成新的类型,所以才得到dynamic_cast判定该类型已经不是原来的类型结果,转换得到了一个空指针。
总得说来,static_cast和reinterpret_cast运算符要么直接被编译器拒绝进行转换,要么就一定会得到相应的目标类型的值。 而dynamic_cast却会进行判别,确定源指针所指的内容,是否真的合适被目标指针接受。如果是否定的,那么dynamic_cast则会返回null。这是通过检查"运行期类型信息"(Runtime type information,RTTI)来判定的,它还受到编译器的影响,有些编译器需要设置开启才能让程序正确运行(导师的PPT详细介绍了Visual Studio的情况),因此dynamic_cast也就不能用传统的转换方式来实现了。
虚函数(virtual function)对dynamic_cast的作用
已经在前面反复提到过面向对象的多态性,但是这个多态性到底要如何体现呢?dynamic_cast真的允许任意对象指针之间进行转换,只是最后返回个null值来告知转换无结果吗?
实际上,这一切都是虚函数(virtual function)在起作用。
在C++的面对对象思想中,虚函数起到了很关键的作用,当一个类中拥有至少一个虚函数,那么编译器就会构建出一个虚函数表(virtual method table)来指示这些函数的地址,假如继承该类的子类定义并实现了一个同名并具有同样函数签名(function siguature)的方法重写了基类中的方法,那么虚函数表会将该函数指向新的地址。此时多态性就体现出来了:当我们将基类的指针或引用指向子类的对象的时候,调用方法时,就会顺着虚函数表找到对应子类的方法而非基类的方法。
当然虚函数表的存在对于效率上会有一定的影响,首先构建虚函数表需要时间,根据虚函数表寻到到函数也需要时间。
因为这个原因如果没有继承的需要,一般不必在类中定义虚函数。但是对于继承来说,虚函数就变得很重要了,这不仅仅是实现多态性的一个重要标志,同时也是dynamic_cast转换能够进行的前提条件。
假如去掉上个例子中Stranger类析构函数前的virtual,那么语句
Children* child_r = dynamic_cast<Children*> (stranger_r);
在编译期就会直接报出错误,具体原因不是很清楚,我猜测可能是因为当类没有虚函数表的时候,dynamic_cast就不能用RTTI来确定类的具体类型,于是就直接不通过编译。
这不仅仅是没有继承关系的类之间的情况,如果基类或者子类没有任何虚函数(如果基类有虚函数表,子类当然是自动继承了该表),当他们作为dynamic_cast的源类型进行转换时,编译也会失败。
这种情况是有可能存在的,因为在设计的时候,我们可能不需要让子类重写任何基类的方法。但实际上,这是不合理的。导师在讲解多态性的时候,时刻强调了一点:如果要用继承,那么一定要让析构函数是虚函数;如果一个函数是虚函数,那么在子类中也要是虚函数。
我会将导师关于"为何继承中析构函数必须是虚函数"的讲解总结一下,当然你也可以看这边文章来了解原因。
索引目录
再谈为何会有那四个转换运算符
看起来,我应该把导师讲过、遗漏的有关C++类型转换方面的内容都总结成文了,主要内容都在以上几篇文章中阐述完毕。
上边的每一篇文章,虽然都单独着重强调一种转换方式或运算符,但是也有提到跟其他运算符之间的差异性,以及使用建议,因此基本可以看出各个运算符的使用方式和作用。
在文章也看到const_cast, reinterpret_cast, static_cast都可以用传统的转换方式基于指针进行替代。如果结合typeid运算符,那么dynamic_cast其实也可以用传统转换方式实现。
因此不免会有疑惑:这四个转换运算符不是很多余。
的确,我刚接触这些转换运算符的时候,我也又这样的疑虑。因为在跟着导师学习C++,做一些课程项目的过程中,我都不曾使用过这些转换运算符。一来我需要用到类型转换的地方很少,二来也还不熟悉这些运算符,所以就尽量避免使用。
不过在花了这么多时间和精力研究和总结这些转换运算符之后,我以后一定会更多的去使用他们,因为传统的转换方式能够实现标准转换运算符的功能,主要还是基于"C++中的指针可以无条件互相转换"。因此,对于转换符的实现,其格式基本都是一致的:用强制转换的方式,直接转换指针类型。
正因如此,看到这些转换代码,有时候并不能马上理解其目的用意。而如果用转换运算符来操作,就可以一目了然地通过转换符的名称知道是在去除const,还是想进行指针的重新定义。
另一个角度来说,编译器也对转换运算符做来限制、优化和异常处理,使用他们可以更好地减少错误的产生,以及避免传统转换没有达到预期的目的。
所以,如果碰到需要类型转换的地方,就尽量思考,是否可以用转换运算符来替代,用哪个是最合适的。下边就来讲讲什么时候用什么样的转换符最合适。
转换运算符的应用之所
结合网络上各个站点看到的关于C++转换符的知识,以及前面那些文章得到的反馈,可以将各个转换运算符的使用总结如下:
对于传统的转换方式(C式或函数式),只在数值类型(包括整型、浮点型、字符类型和枚举)上使用。这也是延续C的形式,当然这类转换也是可以用static_cast来替换,但是因为是基本类型,所以传统转换已经很直观。
对于const_cast转换运算符,用在需要去除掉const限定的时候。其实这种情况出现的很少,可能的方法在const_cast一文中已经又举例,不过还是反复强调, 使用const_cast转换后,绝对不可试图修改结果的值。
对于reinterpret_cast转换运算符,一般用在将对象指针类型转换到整数类型或者void * (空指针)。如同在文中举出的隐患,因此注意的是,若要使用其结果,一定要将类型转换回去后使用。也不要将随意的整数转换成指针类型。
对于static_cast转换运算符,将其用在对象的转换之上(虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧), static_cast会调用相应的构造函数或者重载的转换运算符。
通过文章的留言反馈,以及Google C++ Style Guide的推荐格式,知道对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降。
对于dynamic_cast转换运算符,将其用在具有继承关系的指针类型之间的转换。无论是从基类到子类的转换,还是子类到基类的转换,都将dynamic_cast套上去,也算是标识它们是一家子。
如果任何一种基于指针或引用的转换,套上四个转换运算符之后都失败,那么所要进行的转换可能就触到了"雷区"了:进行了没意义的转换。比如,对于没有关系的两个类型的指针进行了转换,比如试图转换指向方法的指针了。所以转换运算符对于避免代码出错也很有帮助。
基于引用(Reference)的转换运算符使用
前面的文章中,所以对于转换运算符的讲述和举例,都是基于指针的。但实际上,这些转换运算符也可以基于引用来展开。准确说实际上引用类型应该是作为转换的目标类型,源类型则是对象变量(当然也可能用的是引用变量,或是取出指针所指的内容,它们用到的都是实际的类对象)。
由于引用类型“定义时必须初始化”的特别,使得它不同于指针类型随时随地都调用转换运算符,基于引用的转换只在对引用进行初始化的时候才会出现。
下边是const_cast和reinterpret_cast基于引用的运用:
const int int_constant = 21;
int& int_ref = const_cast<int&>(int_constant);
cout << int_ref << endl;
int int_value = 7;
//long& long_ref = int_value; //Error, can not using reference cross types
float& long_ref = reinterpret_cast<float&> (int_value);
cout << long_ref << endl;
对于dynamic_cast的应用基本也是一致的,只是还是限制在具有继承关系的类型之间。不同于基于指针在转换时返回null,dynami_cast在基于引用转换失败时,会抛出std::bad_cast异常,因为不能将空值赋给引用类型。如果要抓住这个异常,则需要引入如下头文件:
#include <typeinfo>
而static_cast转换符前面已经说过推荐直接用在对象之上,不用在指针上,所以也不太会有需要用在引用类型上的情况出现。
山寨C#的TryParse
C#中有很多简洁实用的转换方法,比如从字符串到数值类型的Parse和TryParse,还有包含了各种从object对象到数值类型、时间类型的方法的Convert类,以及检查继承关系的as运算符。
从返回的结果看,C++和dynamic_cast和C#的as很相似,两者都是在失败时候返回null。
不过面向对象的关键点在于什么都是以对象为操作单位,如前所讲dynamic_cast看起来更像是一个全局方法。因此我便模仿C#的数值类的TryParse方法,写了一个包裹dynamic_cast的类型转换方法:
/
// dynamic_cast_tryparse.cpp
// Language: C++
// Complier: Visual Studio 2010, Xcode3.2.6
// Platform: MacBook Pro 2010
// Application: none
// Author: Ider, Syracuse University, ider.cs@gmail.com
///
#include <string>
#include <iostream>
using namespace std;
class Parents
{
public:
Parents(string n="Parent"){ name = n;}
virtual ~Parents(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my children." << endl;
}
void Work()
{
cout << "\tI am " << name <<", I need to work for my family." << endl;;
}
/************** TryParseTo **************/
template<typename T> bool TryParseTo(T** outValue)
{
T* temp = dynamic_cast<T*> (this);
if (temp == NULL) return false;
*outValue = temp;
return true;
}
protected:
string name;
};
class Children : public Parents
{
public:
Children(string n="Child"):Parents(n){ }
virtual ~Children(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my parents." << endl;
}
/*
**Children inherit Work() method from parents,
**it could be treated like part-time job.
*/
void Study()
{
cout << "\tI am " << name << ", I need to study for future." << endl;;
}
private:
//string name; //Inherit "name" member from Parents
};
class Stranger
{
public:
Stranger(string n="stranger"){name = n;}
virtual ~Stranger(){}
void Self_Introduce()
{
cout << "\tI am a stranger" << endl;
}
void Speak()
{
//cout << "I am a stranger" << endl;
cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;
}
private:
string name;
};
int main()
{
Children * parsedChild;
Parents * parsedParent;
Stranger * parsedStranger;
Parents * mother = new Parents("Mother who pretend to be a my daugher");
if(mother->TryParseTo<Children>(&parsedChild))
parsedChild->Speak();
else
cout << "Parents parse to Children failed" << endl;
delete mother;
mother = new Children("Daughter who pretend to be a my mother");
if(mother->TryParseTo<Children>(&parsedChild))
parsedChild->Speak();
else
cout << "Parents parse to Children failed" << endl;
delete mother;
Children * son = new Children("Son who pretend to be a my father");
if(son->TryParseTo<Parents>(&parsedParent))
parsedParent->Speak();
else
cout << "Children parse to Parents failed" << endl;
if(son->TryParseTo<Stranger>(&parsedStranger))
parsedStranger->Speak();
else
cout << "Children parse to Stranger failed" << endl;
delete son;
//pointer of child class could pointer to base class object
/*
* son = new Parents("Father who pretend to be a my son");
if(son->TryParseTo<Parents>(&parsedParent))
parsedParent->Speak();
else
cout << "Parse failed" << endl;
delete son;
*/
return 0;
}
/********************* Result *********************/
//Parents parse to Children failed
// I am Daughter who pretend to be a my mother, I love my parents.
// I am Son who pretend to be a my father, I love my parents.
//Children parse to Stranger failed
这段代码中使用到的类跟dynamic_cast一文中使用的基本一样,只是在基类中多了一个模板方法:
template<typename T> bool TryParseTo(T** outValue)
{
T* temp = dynamic_cast<T*> (this);
if (temp == NULL) return false;
*outValue = temp;
return true;
}
该方法需要指定的目标类型,将自身指针转换成目标指针。转换成功,则将结果赋值给相应的变量,并返回真值;若失败则返回假值,不改变指针变量。因为要让外部的指针变量能够接受到改值,因此不得不使用指向指针的指针。
因为在基类中以公共结果的形式出现,所以每一个子类都继承了该方法,无论是基类的对象还是子类的对象都可以调用该方法。而该方法又不是虚方法,因此不并不希望子类去修改它。只是因为方法是模板方法,可能在编译的时候需要多花一些时间。
由于引用必须在定义时就赋值,并且dynamic_cast对于基于引用的转换不成功时将抛出异常,因此对于基于引用的转换,我还没有想出有什么好的山寨形式。
从测试代码的结果也可以看出,对于该发放的调用都是成功有效的。
所以又应了导师常说的一句话:When use C++, the good news is that you can do everything you want, the bad news is that you have to do everything you want.