C++是一种强类型的编程语言,为了便于在不同类型之间相互转换,C++提供了四种类型转换操作符:dynamic_cast, reinterpret_cast, static_cast, const_cast。

C-style类型转换

(new_type) expression 或 new_type(expression)

对于简单类型来说,这种转换可以很好地工作,例如:

double d = 3.14;
int i = int(d); // i == 3
double d2 = (double)i; // d2 == 3.0

但如果涉及到类和指针的类型转换,这种方式往往不能正确地工作,甚至会导致不可预知的错误,例如:

#include<iostream>
using namespace std;
class A
{
    double a;
public:
    A(double _a) :a(_a) {}
    double get()
    {
        return a;
    }
};
class B
{
    int b;
public:
    B(int _b) :b(_b) {}
};
int main()
{
    B b(3);
    A *a = (A*)&b;
    cout << a->get() << endl;
    return 0;
}

上述代码企图将B类型的变量转换成A类型的变量,并访问变量的成员函数,这无疑会导致运行时无法预知的结果,但编译器无法在编译期间报告这样的错误,因此这种类型转换容易导致难以发现的错误。

dynamic_cast

dynamic_cast<new_type>(expression)

dynamic_cast只能用于对象的指针和引用,可以确保类型转换之后的目标指针指向有效的对象。

在一个继承体系中,dynamic_cast显然可以从子类向父类转换。但dynamic_cast同时还可以做安全的向下类型转换(仅限于带有虚函数的类型),即如果一个父类的指针指向子类的类型,dynamic_cast可以将父类的指针强制转换成子类的指针。

#include<iostream>
using namespace std;
class Base
{virtual void fun() {} };
class Derived: public Base {};
class DDerived: public Derived {};
int main()
{
    Base *pb = new Derived;
    Derived *pd;
    DDerived *pdd;
    pd = dynamic_cast<Derived*>(pb); // ok
    cout << "From pb to pd: " << (pd ? "ok.\n": "error.\n");
    pdd = dynamic_cast<DDerived*>(pb); // error
    cout << "From pb to pdd: " << (pdd ? "ok.\n": "error.\n");
    return 0;
}

上述代码企图将Derived类型的变量强制转换成DDerived类型,dynamic_cast返回一个空指针表明pb指向的对象不是一个完整的DDerived类型(但该对象是一个完整的Derived类型)。 如果dynamic_cast被用来强制转换类型的引用,当转换失败时会抛出bad_cast异常。

注意:dynamic_cast需要运行时类型信息(RTTI)来确定类型转换是否可行,需要编译器支持才能正常工作。并且dynamic_cast是有运行时开销的。

dynamic_cast还可以将空指针转换成任意类型的指针,或者将任意类型的指针转换成void*。

static_cast

static_cast<new_type>(expression)

static_cast可以将子类指针转换为父类指针,也可以将父类指针转换为子类指针,且不进行安全性检查,但不能将指针在完全不相关的类型之间转换,例如不能将Base*直接转换为int*。

static_cast还可以执行以下类型转换:

  1. 基本类型之间的转换;
  2. 将void*转换为任意指针类型,或将任意指针类型转换为void*;
  3. 调用类型转换运算符;
  4. 将变量转换为右值引用;
  5. 将枚举类型转换为整型或浮点型;
  6. 将任意类型转换为void类型。
#include <iostream>
#include <vector>
using namespace std;
 
class C
{
    int c;
public:
    C(){}
    operator int()
    { return c; }
};
enum class E { zero, one, two, three };
 
int main()
{
    // 基本类型之间的转换
    int i = static_cast<int>(3.14); // 3
    double d = static_cast<double>(i); // 3.0
 
    // void*与任意指针之间的转换
    int *pa = new int(0);
    void *pv = static_cast<void*>(pa);
    double *pd = static_cast<double*>(pv);
    delete pa;
 
    // 类型转换运算符
    C c;
    int j = static_cast<int>(c);
 
    // 转换为右值引用
    vector<int> v1({1, 2, 3});
    // value in v1 moved to v2
    vector<int> v2 = static_cast<vector<int>&&>(v1);
 
    // 枚举类型与整型
    int k = static_cast<int>(E::one); // k == 1
 
    // 任意类型转换为void类型
    int a, b;
    static_cast<void>(a);
    return 0;
}

reinterpret_cast

reinterpret_cast<new_type>(expression)

reinterpret_cast用于在任意两种指针之间转换,且不进行任何安全性检查,即使两种指针之间没有任何的继承关系,转换的结果是原指针二进制数值的直接拷贝。同时还可以在整型和指针类型之间进行类型转换。

#include <iostream>
using namespace std;
class A{};
class B{};
int main()
{
    A *a = new A;
    B *b = reinterpret_cast<B*>(a);
    intptr_t p = reinterpret_cast<intptr_t>(b);
}

尽管上述代码可以编译通过,但对b的解引用会造成未知的错误,reinterpret_cast只负责转换,转换的安全性应当由代码的编写者保证。

const_cast

const_cast<new_type>(expression)

const_cast用于增加或去除指针或引用的const属性。

#include <iostream>
using namespace std;
void f(int *pa)
{
    cout << *pa << endl;
}
int main()
{
    const int a=0;
    int *b = const_cast<int*>(&a);
    f(b);
    return 0;
}

尽管我们可以使用const_cast去除const属性,但对该变量的修改依然是未定义的行为,const_cast的(其中一个)用途为:当函数接口只接受非const指针且函数保证不对数据进行修改,但我们手上只有一个数据的const指针,此时需要const_cast去除const属性以供函数使用。

总结

对于基本数据类型(整型、浮点型、布尔型)之间的转换,使用C类型的类型转换便可以清晰明了地表达真正的意图;对于自定义类型之间的转换,考虑dynamic_cast和static_cast;除非必须,尽量不要使用reinterpret_cast在无关的指针类型之间转换,以防引入不必要的麻烦;尽管const_cast可以去除指针的const属性,对一个const变量的修改依然是一个错误的行为,任何时候都不要这样做。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注