C++中的显式类型转换

写C++代码的时候,有时候不可避免的会使用类型转换,良好的编码风格中应该避免隐式转换,隐式转换有时候会产生不易察觉的问题。C++提供了四种显示类型转换方式,当然显示的强制类型转换也是需要尽量避免的。

四种显示转换具有相同的形式

cast_name<type>(expression)

type:是转换的目标类型
expression:是要转换的值
const_name:可以是static_cast、dynamic_cast、const_cast和reinterpret_cast四种中的一种

注意:如果type是引用类型,则结果是左值。

1. static_cast

static_cast是用的最多的转换方式,任何具有明确定义的类型转换,都要不包含底层const,都可以使用,示例如下:

int j = 12;
double result = static_cast<double>(j)/1.2;

像上面这种转换也可以使用隐式类型转换,不过,编译器会给出警告,一般工作中都会把所有的警告消除,或者把警告作为错误处理,防止因为警告产生的错误导致运行结果不正确。

static_cast也用于编译器无法自动执行的类型转换。例如我们把void*指针转换成初始的类型指针:

double d = 12.34;
void *p = &d;
double *dp = static_cast<double*>(p);

我们知道任何非常量对象的地址都可以存入void*,所以我们可以把double类型的对象地址存入void*,然后使用static_cast将其转换成原来的类型。

注意,一定要确保转换后的类型就是指针所指的类型,类型不符的话,产生的结果将是未定义的。

2. dynamic_cast

dynamic_cast支持运行时识别。运行时类型识别(run-time type identification,RTTI)的功能有两个运算符实现:

  • typeid,用于返回表达式的类型;
  • dynamic_cast,用于将基类的指针或引用安全地转换成派生类的指针或引用。

dynamic_cast的使用形式如下:

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

type必须是个类类型,并且通常情况下该类型应该含有虚函数。

  • 在第一种形式中,e必须是个有效的指针;
  • 在第二种形式中,e必须是个左值;
  • 在第三种行驶中,e不能是左值。

在上面的所有形式中,e的类型必须符合以下三个条件中的一个:

  • e的类型是目标type的公有派生类
  • e的类型是目标type的公有基类
  • e的类型就是目标type的类型

如果符合的话,则转换成功,否则转换失败。

如果一条 dynamic_cast语句的转换目标是指针类型失败并且失败了,则结果为0;如果转换目标是引用类型并且失败了,dynamic_cast会抛出一个bad_cast异常。

2.1 指针类型的dynamic_cast

假定Base类至少有一个虚函数,Derived是Base的公有派生类,如果有一个指向Base的指针bp,则我们可以在运行时将它转换成Derived类型的指针:

if (Derived *dp = dynamic_cast<Derived*>(bp)) {
    // 使用dp指向的Derived对象
}else {
    // 使用bp指向的Base对象
}

如果转换失败的话,dp为0,这时执行else分支。

我们可以对一个空指针执行dynamic_cast操作,结果是所需类型的空指针。

注意上面在if语句中定义了dp,这样做的好处是可以在一个操作中同时完成类型转换和条件检查。而且,指针dp在if语句外部是不可访问的。一旦转换失败,后续的代码即使忘了做相应判断,也不会接触到这个未绑定的指针,从而确保程序是安全的。

2.2 引用类型的dynamic_cast

引用类型的dynamic_cast和指针类型的dynamic_cast在转换发生错误时表现略有不同。因为不存在所谓的空引用,所有对于引用类型来说,无法使用与指针类型完全相同的错误处理方式。对引用类型的类型转换失败时,程序抛出一个名为std::bad_cast的异常,该异常定义在typeinfo头文件中。

我们使用如下方式改写上面的程序:

void f(const Base &b) {
    try {
        const Derived &d = dynamic_cast<const Derived&>(b);
        // 使用d引用的Derived对象 
    } catch (bad_cast) {
        // 处理类型转换失败的情况
    }
}

3. const_cast

const_cast用于改变对象的底层const。

顶层const(top-level const)表示指针本身是个常量;
底层const(low-level const)表示指针所指的对象是一个常量。
int i = 0;
int *const p1 = &i; // 不能改变p1的值,这个一个顶层const
const int ci = 42;  // 不能改变ci的值,这是一个顶层const
const int *p2 = &ci;// 允许改变p2的值,这是一个底层const
const int *const p3 = p2; // 靠右的const是顶层const,靠左的是底层const
const int &r = ci;        // 用于声明的const都是底层const

对于将常量转换成非常量的行为,称之为“去掉const性质”。一旦我们去掉了const性质,编译器就不再阻止我们对该对象进行写操作了。

如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为,如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。

const char *pc;
char *p = const_cast<char*>(pc); // 正确,但是通过p写值是未定义的行为

只有const_cast能改变表达式的常量属性。不能使用const_cast改变表达式的类型。

const char *cp;
char *q = static_cast<char*>(cp); // 错误,不能转换掉const性质
static_cast<string> cp; // 正确:字符串字面值转换成string类型
const_cast<string>(cp); // 错误:const_cast只改变常量属性

4. reinterpret_cast

reinterpret_cast是一种更加底层的转换,reinterpret意为重新解释。它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。对它的使用要非常谨慎,实际上,这种转换很少使用。

举一个简单的例子:

int *ip;
char *pc = reinterpret_cast<car*>(ip);

这里pc指向的对象是一个int,不是char,如果把pc当做普通的字符指针使用可能会在运行时发生错误。例如,以下语句可能导致异常的运行时行为:

string str(pc);

使用reinterpret_cast非常危险。用pc出事str的例子很好地证明了这点,其中的关键问题是类型改变了,但编译器没有给出任何警告或者错误的提示信息。当我们用一个int的地址初始化pc时,由于显式地说明这种转换合法,所以编译器不会发出任何警告或任何错误信息。接下来再使用pc时就会认定它的值是char*类型,编译器没法知道它实际存放的是int的指针。

最终的结果就是,上面的例子用pc初始化str没什么实际意义,甚至还可能引发更糟糕的结果,但仅从语法上这种操作是没有问题的,查找这类问题非常困难。

从上面的描述可以看出,C++为我们提供了多种转换方式,但是还是建议尽量少用。在无法避免的情况下,使用显式转换优于隐式转换,reinterpret_cast强烈建议不要使用。


更详细的内容请查阅《C++ Primer》第5版。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值