目录
4.1.1 const_cast的基本使用,修改类型的const或volatile属性。
4.4 reinterpret_cast (expression)
4.4.1 reinterpret_cast应用之用来辅助哈希函数
一、类型转化的四种方式
1.去常转换:const_cast<new_type>(expression)
2.静态转换:static_cast<new_type>(expression)
3.动态转换:dynamic_cast<new_type>(expression)
4.重新解释:reinterpret_cast<new_type>(expression)
其中,new_type为目标数据类型,expression为变量或者表达式
二、C语言强制类型转换的缺点
1.没有从形式上体现转换功能和风险的不同。
例如,将 int 强制转换成 double 是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。
2.将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
3.难以在程序中寻找到底什么地方进行了强制类型转换。
强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。
如果采用C语言的老式做法,要在程序中找出所有进行了强制类型转换的地方,显然是很麻烦的,因为这些转换没有统一的格式。
而用 C++ 的方式,则只需要查找_cast字符串就可以了。甚至可以根据错误的类型,有针对性地专门查找某一种强制类型转换。例如,怀疑一个错误可能是由于使用了 reinterpret_cast 导致的,就可以只查找reinterpret_cast字符串。
三、每个类型转换的适用场景
1. dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。支持父类指针到子类指针的转换,这种转换时最安全的转换。它 是唯一不能用旧风格语法执行的强制类型转换,也是唯一可能有重大运行时代价的强制转换。
2.static_cast 可以被用于强制隐形转换(例如,non-const对象转换为const对象,int转型为double,等等),它还可以用于很多这样的转换的反向转换 (例如,void*指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个const对象转型为non-const对象(只有 const_cast能做到),它最接近于C-style的转换。应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类。
3.const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制转型。这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。
4.reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制类型在底层代码以外应该极为罕见。操作 结果只是简单的从一个指针到别的指针的值得二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
四、每个类型转换的使用以及注意事项
4.1 const_cast<new_type>(expression)
4.1.1 const_cast的基本使用,修改类型的const或volatile属性。
const_cast <type_name> (expression)
返回值为新类型。主要用于去掉const属性,该运算符用来修改类型的const(唯一有此能力的C+±style转型操作符)或volatile属性.type_name必须是指针*或引用&或成员指针类型。
代码演示
#include<iostream>
struct type {
int i;
type(): i(3) {}
void f(int v) const
{
// this->i = v; // 编译错误:this 是指向 const 的指针
const_cast<type *>(this)->i = v; // 只要该对象不是 const 就 OK
}
};
int main()
{
int i = 3; // 不声明 i 为 const
const int &rci = i;
//rci = 100; //错误
const_cast<int &>(rci) = 4; // OK:修改 i
std::cout << "i = " << i << '\n';
type t; // 如果这是 const type t,那么 t.f(4) 会是未定义行为
t.f(4);
std::cout << "type::i = " << t.i << '\n';
const int j = 3; // 声明 j 为 const
int *pj = const_cast<int *>(&j);
*pj = 4; // 未定义行为
std::cout << "j = " << j << '\n';
std::cout << "*pj = " << *pj << '\n';
void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
//const_cast<void(type::*)(int)>(pmf); // 编译错误:const_cast 不能用于成员函数指针
}
运行输出:
i = 4
type::i = 4
j = 3
*pj = 4
上述代码介绍了const_cast常用的使用场景和使用的注意事项。其中:
const int j = 3; // 声明 j 为 const
int *pj = const_cast<int *>(&j);
*pj = 4; // 未定义行为
std::cout << "j = " << j << '\n'; //输出结果:j = 3
std::cout << "*pj = " << *pj << '\n'; //输出结果:*pj = 4
对于&j使用const_cast转换后,进行重新赋值,这种行为在C++语法中是未定义的行为,但实际上是确实可以运行的。但对于打印输出结果却与我们的预期不一致,实际上对于j的值真的没有修改成功吗?下面我们继续做一个实验,代码如下:
#include<iostream>
int main()
{
//未定义的行为,不提倡使用
const int j = 3; // 声明 j 为 const
int *pj = const_cast<int *>(&j);
*pj = 4; // 未定义行为
std::cout << "j = " << j << " ,addr(j):" << &j << '\n';
std::cout << "*pj = " << *pj << " ,addr(*pj):" << pj << '\n';
//正常的行为
int j1 = 3;//最初声明为非const
const int *cpj1 = &j1;
int *pj1 = const_cast<int *>(cpj1);//cpj1最终指向的值(即j1的值)为非const类型,可以使用const_cast
*pj1 = 4;
std::cout << "j1 = " << j1 << " ,addr(j1):" << &j1 << '\n';
std::cout << "*pj1 = " << *pj1 << " ,addr(*pj1):" << pj1 << '\n';
}
运行结果:
j = 3 ,addr(j):0x30932680c
*pj = 4 ,addr(*pj):0x30932680c
j1 = 4 ,addr(j1):0x3093267fc
*pj1 = 4 ,addr(*pj1):0x3093267fc
从运行结果可以看出,j和*pj的地址相同,j1和*pj1的地址相同,但是j和*pj显示的值却不同,为什么会出现这种结果呢?实际上这就是因为编译器优化结果造成的,因为在声明j的时候,其类型是const int,在编译阶段,编译器认为它就是不变的类型,当编译到std::cout << "j = " << j << " ,addr(j):" << &j << '\n';时,会将j直接替换为常量3,即std::cout << "j = " << 3 << " ,addr(j):" << &j << '\n';,因此打印出来的就过就是3。也正是由于该行为是未定义的行为,才导致输出结果与我们的预期不一致。所以,在我们日常使用中,const_cast可以用用来修改最初声明非const的值,而且应该尽量避免常量转换,除非我们真的需要使用它。
4.1.2 const_cast在调用第三方函数中的使用
const_cast另外一种使用场景就是:在使用第三方库或API时,它们只提供了非const类型的参数的函数,但我们只有const类型的对象。如下例所示。
#include <iostream>
using namespace std;
int third_lib_fun(int *ptr) {
*ptr = *ptr + 10;
return (*ptr);
}
int main(void) {
int val = 10;
const int *ptr = &val;
int *ptr1 = const_cast<int *>(ptr);
third_lib_fun(ptr1);
cout << val;
return 0;
}
输出结果:
20
在上例中,我们在使用第三方库和API的时候,我们只能调用,看不到其具体的实现,为了能够调用成功,需要使用const_cast来去除*ptr的const属性,来保证函数的正常调用,但是需要保证*ptr指向的对象在初始化的时候是非const的。
4.1.3 const_cast去除volatile属性
const_cast的另一个作用就是:const_cast可以用于丢弃volatile属性。例如在下面的示例中,通过const_cast将b1的类型由volatile int*转换为 int*。
#include <iostream>
#include <typeinfo>
using namespace std;
int main(void) {
int a1 = 40;
volatile int* b1 = &a1;
cout << "typeid of b1 " << typeid(b1).name() << '\n';
int* c1 = const_cast<int*>(b1);
cout << "typeid of c1 " << typeid(c1).name() << '\n';
return 0;
}
输出结果:
typeid of b1 PVi
typeid of c1 Pi
从输出结果可以看到,b1的typeid为PVi(pointer to a volatile integer,指向volatile类型的int指针),c1的typeid为Pi(Pointer to integer,指向int的指针),使用const_cast去除了b1的volatile的属性。
4.2 static_cast<new_type>(expression)
static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性
4.2.1 static_cast用于基本类型数据转换
char a = 'a';
int b = static_cast<char>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
4.2.2 static_cast用于类上行和下行转换:
class Base
{};
class Derived : public Base
{}
Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB))
{}//下行转换是不安全的(坚决抵制这种方法)
Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD))
{}//上行转换是安全的
4.2.3 static_cast总结(单向变双向)
转换合法规则, 以下条件只要满足任一条,则转换合法:
-
new_type可被隐式转换为expression所属的类型 ;
-
expression可被隐式转换为new_type所属的类型.
High 是一个基类 ,Low是 High类的一个派生类,有如下代码:
High bar; //基类
Low blow; //派生类
...
High *pb = static_cast<High *>(&blow); //blow是派生类,pb是基类的指针,因此这里是向上转换,合法【基类指针本身就可指向派生类指针】
Low *pl = static_cast<Low *>(&bar); //bar是基类,pl是派生类指针,因此是向下转换,合法【但是要注意向下转换虽合法,但不安全,因为向下转换,访问到成员变量可能会崩溃,一般使用动态转换】
为什么上述两个转换都是合法的呢?
其实原因就一条,我们都知道,在派生关系中, 基类的指针可以直接指向一个派生类的对象,这个过程不需要显式转换 ,因此结合上述的转换合法原则,我们来分析一下上述两句转换的合法性:
-
High *pb = static_cast<High *>(&blow); // High *是 type_name ,&blow所属的类型是Low * , 因为 High* 可以被隐式的转换为Low* ,这符合转换合法原则的前种情况 " type_name可被隐式转换为expression所属的类型" ,因此转换合法;
-
Low *pl = static_cast<Low *>(&bar); // Low *是 type_name ,&bar所属的类型是High * ,还是因为 High* 可以被隐式的转换为Low* ,这符合转换合法原则的后种情况 "expression可被隐式转换为type_name所属的类型",因此转换合法.
因此,我们可以看到static_cast可以把原来只允许单向转换的场景变成允许双向转换,类似的情况有:
-
一般来说,可以把任意的数据类型指针赋值给void *指针 ,但是不能把 void *指针赋值给任意数据类型的指针, 如果使用了static_cast ,那么就可以实现把void * 赋值给 任意数据类型;
-
一般来说, 我们可以把int类型赋值给double类型,但是不能把 double类型的赋值给int类型 ,但如果使用了static_cast ,那么就可以把double类型赋值给int类型
-
一般来说,我们可以把一个枚举类型enum直接赋值给int ,但是不能把int直接转换为enum,但如果使用了static_cast ,那么就可以实现这种转换
类似的情况,大家可以继续发散..总之 ,原则就一条 :
能不能合法转换,主要取决于new_type和expression之间要存在任一方向的隐式转换关系.
但是至于转换后的内容是否安全,这是开发需要自行保证的,编译器无法保证.
4.3 dynamic_cast<new_type>(expression)
dynamic_cast主要用于类层次结构中父类和子类之间指针和引用的转换,由于具有运行时类型检查,因此可以保证下行转换的安全性,何为安全性?即转换成功就返回转换后的正确类型指针,如果转换失败,则返回NULL,之所以说static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL。
dynamic_cast转换方式主要有三种:
dynamic_cast< type* >(e) //type必须是一个类类型且必须是一个有效的指针,类中层次转换最常用的方式
dynamic_cast< type& >(e)//type必须是一个类类型且必须是一个左值
dynamic_cast< type&& >(e)//type必须是一个类类型且必须是一个右值
e的类型必须符合以下三个条件中的任何一个:
-
e是type的公有派生类
-
e是type的共有基类
-
e是type的类型
转换结果:
-
如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。
-
如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。
-
e也可以是一个空指针,结果是所需类型的空指针。
-
【比较】static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。
-
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的【父类指针本身就可指向一个子类对象】;
-
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
-
dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作
代码举例
class Base{//抽象基类
virtual void fun(){}
};
class Derived:public Base{//派生类
};
//********************************
Base *P = new Derived();
Derived *pd1 = static_cast<Derived *>(P);//不建议该方式,可能存在转换不安全情况
Derived *pd2 = dynamic_cast<Derived *>(P);
转换结果:
-
如果 P指向的确实是子类对象,则dynamic_cast和static_cast都可以转换成功;
-
如果 P 指向的是父类对象时,static_cast转换不安全;
如果 P 指向的是父类对象时,static_cast转换在编译时不会报错,但也可以返回一个子类对象指针(假想),这样是不安全的,在运行时可能会有问题,因为子类中包含父类中没有的数据和函数成员,这里需要理解转换的字面意思,转换是什么?转换就是把对象从一种类型转换到另一种类型,如果这时用 pd1 去访问子类中有但父类中没有的成员,就会出现访问越界的错误,导致程序崩溃。而dynamic_cast由于具有运行时类型检查功能,它能检查P的类型,由于上述转换是不合理的,所以它返回NULL。
因此,在面向对象编程开发中,我们常常使用 dynamic_cast<Derived *>(p)语法环境来判断当前对象(一般父类基类对象)是否指向当前派生类:
4.3.1 指针类型:
Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:
if(Derived *dp = dynamic_cast<Derived *>(bp)){
//使用dp指向的Derived对象
}
else{
//使用bp指向的Base对象
}
4.3.2 引用类型
因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。
void f(const Base &b){
try{
const Derived &d = dynamic_cast<const Base &>(b);
//使用b引用的Derived对象
}
catch(std::bad_cast){
//处理类型转换失败的情况
}
}
4.3.3 转换注意事项
C++中层次类型转换中无非两种:上行转换和下行转换
-
对于上行转换,static_cast和dynamic_cast效果一样,都安全;
-
对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,常用dynamic_cast进行转换。虽然static_cast和dynamic_cast都能成功,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL。
4.4 reinterpret_cast <new_type> (expression)
reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释。reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
由此可知,reinterpret_cast虽然看似强大,作用却没有那么广。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。
4.4.1 reinterpret_cast应用之用来辅助哈希函数
// 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 *扩展性也是一样很高的),唯一损失的可能就是存取的时候整形和地址的转换(这完全可以忽略不计)。
- 当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限定
4.4.2 reinterpret_cast的其他应用
// 将指针转换为整数类型
char* p = "example";
std::uintptr_t p_int = reinterpret_cast<std::uintptr_t>(p);
// 将整数类型转换为指针类型
char* p_back = reinterpret_cast<char*>(p_int);
// 将函数指针转换为不同类型的函数指针
void (*func_ptr)() = reinterpret_cast<void (*)()>(some_function);
// 将指向对象的指针转换为不同类型的对象指针
Derived* derived_ptr = reinterpret_cast<Derived*>(base_ptr);

被折叠的 条评论
为什么被折叠?



