最近在看KMCLib源码,里面的类型转换都是使用的C++的类型转换运算符,而不是C语言的类型转换,那我就在这把C++四种类型转换运算符总结下。

C风格(C-Style)强制转型

1
2
3
4
(T) expression  // 将expression类型强制转换成T

// 函数风格
T(expression) // cast expression to be of type T

这两种形式之间没有本质上的不同,纯粹就是形式的区别。

C++的四种强制类型转换

在C++创始人Bjarne Stroustrup看来,C与阿燕的类型转换太过松散,因此采取了措施,就是更严格地限制允许的类型转换,并添加4个类型转换运算符,是转换过程更规范。

  • dynamic_cast
  • const_cast
  • static_cast
  • reinterpret_cast

这样可以根据转换的目的选择类型转换运算符,而不是通用的类型转换,让编译器能够检查程序的行为是否与设计者的想法吻合。

  1. dynamic_cast
    主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。他是唯一不能用就风格语法执行的强制类型转换,也是唯一可能有重大运行时代价的强制类型转换。

    1
    2
    3
    4
    5
    6
    7
    8
    class A { ... };
    class B : public A { ... };
    class C : public B { ... };

    // 定义每种类型的指针
    A * pa;
    B * pb;
    C * pc;

    dynamic_cast的语法:

    1
    B * pb = dynamic_cast<B *>(pa);

    pa的类型是否可被安全的转换为B *? 如果可以则返回转换后的指针,如果不可以则返回一个空指针;
    所谓安全是为了让写程序的人知道自己调用的是哪个类的相应的方法。
    例如A,B,C三个类都有Speak方法,但是B类中定义了一个A类中没有的Say()方法。若我不清楚指针指向的是哪个对象的话我就不知道我能不能通过指针调用Say()方法了。因此我需要验证一下,于是可以用dynamic_cast<B *>看看是不是能转换成功,如果可以,那说明是B类,可以调用其Say()方法。

    1
    2
    3
    4
    5
    B * ps;
    if (ps = dynamic_cast<B *>(pa))
    {
    ps->Say();
    }
  2. const_cast
    一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制类型转换。
    但是const_cast不是万能的,如果一个值在声明的时候就声明为const则可能编译器会禁止修改它。

  3. static_cast
    可以被用于强制隐型转换(例如,non-const 对象转型为const对象, int转型为double, 等等),它还可以用于很多这样的转换的反向转换(例如,void\* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个const对象转型为non-const对象(只有const_cast能做到),它最接近于C-style的转换。

  4. reinterpret_cast
    特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。

旧风格的强制转型依然合法,但是新的形式更可取。首先,在代码中它们更容易识别(无论是人还是像 grep 这样的工具都是如此),这样就简化了在代码中寻找类型系统被破坏的地方的过程。第二,更精确地指定每一个强制转型的目的,使得编译器诊断使用错误成为可能。例如,如果你试图使用一个 const_cast 以外的新风格强制转型来消除常量性。


dynamic_cast .vs. static_cast

1
2
3
4
A * pa = new A;

B * p1 = dynamic_cast<B *>(pa);
B * p2 = static_cast<B *>(pa);

在这里dynamic_cast运算符知道转换时不合法的,于是他将返回空指针。但是static_cast则要依赖写程序的人的判断了,他将会简单的返回一个指向一个B类型的指针,实际指向的对象并不是B类型的。
dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类指针,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性.static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换,窄化变换(这种变换会导致对象切片,丢失信息),用VOID*的强制变换,隐式类型变换等…


static_cast .vs. reinterpret_cast

reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它.我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的.(这句话是C++编程思想中的原话)

static_castreinterpret_cast 操作符修改了操作数类型. 它们不是互逆的; static_cast在编译时使用类型信息执行转换, 在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的. 另一方面, reinterpret_cast仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:

1
2
int n = 9;
double d = static_cast<double>(n);

上面的例子中, 我们将一个变量从int转换到 double. 这些类型的二进制表达式是不同的. 要将整数 9 转换到 双精度整数 9, static_cast 需要正确地为双精度整数 d 补足比特位. 其结果为 9.0. 而reinterpret_cast 的行为却不同:

1
2
int n=9;
double d = reinterpret_cast<double & > (n);

这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析.

总结

一共四种cast

  1. static_cast,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的;
  2. dynamic_cast,支持父类指针到子类指针的转换,并根据实际情况调整指针的值,和static_cast不同,反过来它就不支持了,会导致编译错误,这种转换是最安全的转换;
  3. reinterpret_cast,支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;
  4. const_cast,这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。

Comments

2016-03-18