首页 > 编程笔记 > C++笔记 阅读:3

C++类型转换(static_cast、dynamic_cast、const_cast和reinterpret_cast)

数据经常需要从一种类型转换为另一种类型。有些转换在编译时是必要的(如 double 转换为 int),其他的在运行时是必要的(如类指针在层级里上下转换)。C++ 语言支持以(type)expression 或 type(expression) 形式与 C类型强制转换风格兼容。然而,这种类型的转换打破了 C++ 的类型安全性。

因此,C++ 语言还提供了几个转换:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。它们用于表示更明确的目的和写出更安全的代码。

C++类型转换的使用方式

1) 使用 static_cast 来进行非多态类型的转换,包括从整型到枚举类型、从浮点型到整型或从指针类型到另一指针类型的转换,如从基类到派生类(向下转换)或从派生类到基类(向上转换),但没有任何运行时检查:
enum options {one = 1, two, three};

int value = 1;
options op = static_cast<options>(value);

int x = 42, y = 13;
double d = static_cast<double>(x) / y;

int n = static_cast<int>(d);

2) 使用 dynamic_cast 对多态类型的指针或引用进行从基类到派生类或相反方向的类型转换。在运行时进行检查,需要启用运行时类型信息(Runtime Type Information, RTTI):
struct base {
    virtual void run() {}
    virtual ~base() {}
};

struct derived : public base {
};

derived d;
base b;

base* pb = dynamic_cast<base*>(&d);    // OK
derived* pd = dynamic_cast<derived*>(&b); // fail

try {
    base& rb = dynamic_cast<base&>(d);    // OK
    derived& rd = dynamic_cast<derived&>(b); // fail
}
catch (std::bad_cast const & e)
{
    std::cout << e.what() << '\n';
}

3) 使用 const_cast 在具有不同 const 和 volatile 说明符的类型之间执行转换,如删除未声明 const 对象的 const:
void old_api(char* str, unsigned int size)
{
    // do something without changing the string
}

std::string str{"sample"};
old_api(const_cast<char*>(str.c_str()),
        static_cast<unsigned int>(str.size()));

4) 使用 reinterpret_cast 对类型重启解释,如整型和指针类型的转换、从指针类型到整型的转换,或从指针类型到任何其他指针类型的转换,而不需要运行时检查:
class widget
{
public:
    typedef size_t data_type;
    void set_data(data_type d) { data = d; }
    data_type get_data() const { return data; }
private:
    data_type data;
};

widget w;
user_data* ud = new user_data();
// write
w.set_data(reinterpret_cast<widget::data_type>(ud));
// read
user_data* ud2 = reinterpret_cast<user_data*>(w.get_data());

深度剖析C++类型转换

显式类型转换,有时被称为 C风格转换或静态转换,是 C++ 兼容 C 语言的遗留问题,让你可以执行各种转换,包括:
这种转换在多态类型或模板中不好用。因此,C++ 提供了前述示例中的四种转换。使用这些转换有几个重要的好处:
即使名称可能这样表示了,static_cast 也不直接等同于显式类型转换或静态转换。此转换在编译时进行并可用于隐式转换、反向隐式转换和层级类指针间的转换。它不能用于不相关指针类型间的转换。因此,在以下示例中,使用 static_cast 进行从 int* 到 double* 转换会报编译错误:
int* pi = new int{ 42 };
double* pd = static_cast<double*>(pi);  // compiler error

然而,从 base* 到 derived* 转换不会产生编译错误,但在尝试使用新指针时会报运行时错误:
base b;
derived* pd = static_cast<derived*>(&b);  // compilers OK, runtime error
base* pb1 = static_cast<base*>(pd);       // OK

另外,static_cast 不能用于删除 const 和 volatile 修饰符。以下代码片段举例说明了这一点:
int const c = 42;
int* pc = static_cast<int*>(&c);  // compiler error

使用 dynamic_cast 可安全地对层级表达式进行向上转换、向下转换、沿着层级转换。此转换在运行时进行,并要求开启 RTTI。因此,它有运行时开销。动态类型转换只能用于指针和引用。

当 dynamic_cast 用于将表达式转换为指针类型但操作失败时,结果为 nullptr。当它用于将表达式转换为引用类型但操作失败时,会抛出 std::bad_cast 异常。因此,通常将 dynamic_cast 转换为引用类型的操作放在 try catch 代码块中。

尽管动态类型转换在运行时进行,但如果你尝试对非多态类型进行转换,那么你将会得到一个编译器错误:
struct struct1 {};
struct struct2 {};

struct1 s1;
struct2* ps2 = dynamic_cast<struct2*>(&s1); // compiler error

reinterpret_cast 更像是编译器指示。它不能转换为任何 CPU 指令,它只让编译器将表达式的二进制表示解释为另一种指定类型的二进制表示。这是类型不安全转换,应该小心使用。它可用于整数类型与指针、指针类型、函数指针类型之间的转换。因为不需要检查,所以 reinterpret_cast 可成功地用于转换不相关类型之间的表达式,如从 int* 到 double*,但会产生未定义行为:
int* pi = new int{ 42 };
double* pd = reinterpret_cast<double*>(pi);

reinterpret_cast 通常用于操作系统或特定供应商 API 类型的表达式转换。很多 API 将用户数据以指针或整型进行存储。因此,如果需要将用户定义类型地址传递给上述 API,你需要将不相关指针类型或指针类型值转换为整型类型值。

前面提供了类似的例子,其中 widget 类将用户定义数据存储在数据成员中并提供 set_data() 和 get_data() 方法来访问它。如果需要将指针转换为 widget 中的对象,可如示例中那样使用 reinterpret_cast。

const_cast 跟 reinterpret_cast 类似,它们都是编译器指示,不会转换为 CPU 指令。它用于删除 const 或 volatile 修饰符,这是之前讨论的其他三种转换都做不了的操作。

只有当对象没有声明为 const 或 volatile 时,才应该使用 const_cast 删除 const 或 volatile 修饰符。任何其他情况都会产生未定义行为,如下例所示:
int const a = 42;
int const * p = &a;
int* q = const_cast<int*>(p);
*q = 0; // undefined behavior
在此示例中,变量 p 指向声明为常量的对象(变量 a)。通过删除 const 修饰符来修改指针指向的对象的这种尝试将产生未定义行为。

当以 (type)expression 形式使用显式类型转换时,需要留意,它将在以下列表中选择第一个满足特定转换要求的选项:
const_cast<type>(expression)
static_cast<type>(expression)
static_cast<type>(expression)+const_cast<type>(expression)
reinterpret_cast<type>(expression)
reinterpret_cast<type>(expression)+const_cast<type>(expression)
而且不像特定 C++ 转换,静态转换可用于不完全类类型之间的转换。如果 type 和 expression 都是指向不完全类型的指针,那么将不指定是选择 static_cast 还是选择 reinterpret_cast。

相关文章