C++的4种类型转换

static_cast

用隐式和用户定义转换的组合在类型间转换。

语法

static_cast <new_type> (expression) 返回 new_type 类型的值。

解释

唯有下列转换能用 static_cast 执行,但若转换会转型走常量性易变性则亦不允许

  1. 若存在从 expressionnew_type隐式转换序列,或者若针对以 expressionnew_type 类型的对象或引用所进行的直接初始化的重载决议,找到至少一个可行函数,则 static_cast <new_type> (expression) 返回如同以 new_type Temp(expression); 所初始化的一个虚构变量 Temp,它可能涉及隐式转换,对 new_type构造函数的调用,或对用户定义转换运算符的调用。对于非引用的 new_type,static_cast 纯右值表达式的结果对象是其直接初始化的对象。 (C++17 起)

  2. new_type 是某类类型 D 的左值或指向它的指针纯右值,而 expression 的类型是到其非虚基类 B 的指针或引用,则 static_cast 进行向下转型(downcast)。若 BD 的有歧义、不可访问或虚的基类(或虚基类的基类),则此向下转型非良构。这种 static_cast 并不进行用以确保该对象的运行时类型确实是 D 的运行时检查,而且仅当这项前提条件由其他方法所保证时才能安全使用,例如在实现静多态时。可以用 dynamic_cast 执行安全的向下转型。

    1
    2
    3
    4
    5
    struct B { };
    struct D : B { };
    D d;
    B& br = d;
    static_cast<D&>(br); // 左值指代原初的 d 对象
  3. (本条自C++11起)new_type 是右值引用类型,则 static_cast 将泛左值、类纯右值或数组纯右值 (C++17 前) 任何左值 (C++17 起) expression 的值转换为与该表达式指代相同对象,或指代其基类子对象(取决于 new_type)的亡值。若目标类型是表达式的不可访问或有歧义基类,则程序非良构。若表达式是位域左值,则它首先被转换成底层类型的纯右值。这种 static_cast 用于在 std::move 中实现移动语义。

  4. new_type 是(可为 cv 限定的)void 类型,则 static_cast求值 expression舍弃其值。

  5. 若存在从 new_typeexpression 类型的标准转换序列,且它不包含左值到右值、数组到指针、函数到指针、空指针、空成员指针、函数指针 (C++17 起)或布尔转换,则 static_cast 能进行该隐式转换的逆转换。

  6. 若从 expressionnew_type 涉及左值到右值、数组到指针或函数到指针转换,则能显式用 static_cast 进行。

  7. 有作用域枚举类型(C++11)能转换成整数或浮点类型。

    1. 当目标类型为 cv bool 时,若原值为零则结果为 false,而对所有其他值结果为 true。对于其余整型类型,若该枚举的值能以目标类型表示,则结果为其值,否则结果未指明。(C++20前)
    2. 其结果与从枚举的底层类型隐式转换成目标类型的结果相同。 (C++20 起)
  8. 整数或枚举类型值可转换为任何完整枚举类型。

    • 若底层类型不固定,则当 expression 的值落在范围(范围是大到足以保有目标枚举的所有枚举项的最小位域的所有可能值)外时,结果未指明 (C++17 前) 或 为未定义行为 (C++17 起)。

    • 若底层类型固定,则其结果与转换原值到枚举的底层类型再到该枚举类型的结果相同。

      浮点类型的值也可转换为任何完整枚举类型。

    • 其结果与转换原值到枚举的底层类型再到该枚举类型的结果相同。

  9. 指向某类 D 的成员的指针可以向上转型(upcast)为指向其无歧义、可访问的基类 B 的成员。这种 static_cast 不进行用以确保成员实际存在于所指向对象的运行时类型的检查。

  10. 指向(可有 cv 限定)void 的指针类型的纯右值可转换到指向任何对象的指针类型。若原指针值所表示的内存中字节地址不满足目标类型的对齐要求,则结果指针值未指明。否则,若原指针值指向对象 a,且存在与 a 指针可互转换(定义于下文)的目标类型的(忽略 cv 限定)对象 *b,则结果为指向 *b 的指针。否则指针值不改变。任何指针转换到 void 指针,再转换回原(或更为 cv 限定的)类型的指针,都保持其原值。

同所有转型表达式,结果是:

  • 左值,若 new_type 是左值引用或到函数类型的右值引用;
  • 亡值,若 new_type 是到对象类型的右值引用;
  • 否则为纯右值。

满足以下条件时,两个对象 ab 指针可互转换(pointer-interconvertible)

  • 它们为同一对象,或
  • 一个是 union 对象而另一个是该对象的非静态数据成员,或
  • 一个是标准布局类对象,而另一个是该对象的首个非静态数据成员,或若该对象无非静态数据成员,则为该对象的任何基类子对象,或
  • 存在对象 c 使得 ac 指针可互转换,而 cb 指针可互转换。
1
2
3
4
union U { int a; double b; } u;
void* x = &u; // x 的值为“指向 u 的指针”
double* y = static_cast<double*>(x); // y 的值为“指向 u.b 的指针”
char* z = static_cast<char*>(x); // z 的值为“指向 u 的指针”

static_cast 亦可用于通过进行到指定类型的函数到指针转换,来消解函数重载的歧义。如

1
2
std::for_each(files.begin(), files.end(),
static_cast<std::ostream&(*)(std::ostream&)>(std::flush));

dynamic_cast

沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。

语法

dynamic_cast <new_type> (expression)

new_type:指向完整类类型的指针、到完整类类型的引用,或指向(可选的 cv 限定)void 的指针

expression*:若 *new_type 为引用,则为完整类类型的 左值 (C++11 前) / 泛左值 (C++11 起) 表达式,若 new_type 为指针,则为指向完整类类型的指针纯右值。

若转型成功,则 dynamic_cast 返回 new_type 类型的值。若转型失败且 new_type 是指针类型,则它返回该类型的空指针。若转型失败且 new_type 是引用类型,则它抛出与类型 std::bad_cast 的处理块匹配的异常。

解释

唯有下列转换能用 dynamic_cast 进行,但若这种转换会转换走常量性易变性则亦不允许

  1. expression 的类型恰是 new_typenew_type 的较少 cv 限定版本,则结果是 expression 具有 new_type 类型的值。(换言之,dynamic_cast 可用以添加常量性。隐式转换和 static_cast 亦能进行此转换。)

  2. expression 的值是空指针值,则结果是 new_type 类型的空指针值。

  3. new_type 是到 Base 的指针或引用,且 expression 的类型是到 Derived 的指针或引用,其中 BaseDerived 的唯一可访问基类,则结果是到 expression 所标识的对象中 Base 类子对象的指针或引用。(注意:隐式转换和 static_cast 亦能进行此转换。)

  4. expression 是指向多态类型的指针,且 new_type 是到 void 的指针,则结果是指向 expression 所指向或引用的最终派生对象的指针。

    多态对象:声明或继承了至少一个虚函数的类类型的对象是多态对象

  5. expression 是到多态类型 Base 的指针或引用,且 new_type 是到 Derived 类型的指针或引用,则进行运行时检查:

    a) 检验 expression 所指向/标识的最终派生对象。若在该对象中 expression 指向/指代 Derived 的公开基类,且只有一个 Derived类型对象从 expression 所指向/标识的子对象派生,则转型结果指向/指代该 Derived 对象。(此之谓“向下转型(downcast)”。)

    b) 否则,若 expression 指向/指代最终派生对象的公开基类,而同时最终派生对象拥有 Derived 类型的无歧义公开基类,则转型结果指向/指代该 Derived(此之谓“侧向转型(sidecast)”。)

    c) 否则,运行时检查失败。若 dynamic_cast 用于指针,则返回 new_type 类型的空指针值。若它用于引用,则抛出 std::bad_cast 异常。

  6. 当在构造函数或析构函数中(直接或间接)使用 dynamic_cast,且 expression 指代正在构造/销毁的对象时,该对象被认为是最终派生对象。若 new_type 不是到构造函数/析构函数自身的类或其基类之一的指针或引用,则行为未定义。

与其他转型表达式相似:

  • new_type 是左值引用类型(expression 必为左值),则其结果为左值
  • new_type 是右值引用类型(expression 为完整类类型,可以是左值或右值 (C++17 前) / 必为泛左值(纯右值被实质化) (C++17 起)),则其结果为亡值
  • new_type 是指针类型,则其结果为纯右值

  • 亦可用 static_cast 进行向下转型,它避免运行时检查的开销,但只有在程序(通过某些其他逻辑)能够保证 表达式 所指向的对象肯定是 Derived 时才是安全的。
  • 某些形式的 dynamic_cast 依赖于运行时类型鉴别( RTTI ),即编译的程序中关于每个多态类的信息。编译器通常有选项禁用此信息。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <memory>

struct V {
virtual void f(){}; // 必须为多态以使用运行时检查的 dynamic_cast
};
struct A : virtual V {};
struct B : virtual V {
B(V* v, A* a) {
// 构造中转型(见后述 D 的构造函数中的调用)
dynamic_cast<B*>(v); // 良好定义:v 有类型 V*,B 的 V 基类,产生 B*
dynamic_cast<B*>(a); // 未定义行为:a 有类型 A*,A 非 B 的基类
}
};
struct D : A, B {
D() : B((A*)this, this) {}
};

struct Base {
virtual ~Base() {}
};

struct Derived : Base {
virtual void name() {}
};

int main() {
D d; // 最终派生对象
A& a = d; // 向上转型,可以用 dynamic_cast,但不必须
D& new_d = dynamic_cast<D&>(a); // 向下转型
B& new_b = dynamic_cast<B&>(a); // 侧向转型

Base* b1 = new Base;
if (Derived* d = dynamic_cast<Derived*>(b1)) {
std::cout << "downcast from b1 to d successful\n";
d->name(); // 可以安全调用
}

Base* b2 = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b2)) {
std::cout << "downcast from b2 to d successful\n";
d->name(); // 可以安全调用
}

delete b1;
delete b2;

try {
std::unique_ptr<A> ptr(new A);
D* test_d = static_cast<D*>(ptr.get());
D& test = dynamic_cast<D&>(*test_d); // 转型并不会出错 (见解释1)
std::cout << ptr.get() << std::endl;
std::cout << &test << std::endl;
} catch (const std::bad_cast& e) {
std::cout << e.what() << '\n';
}

try {
std::unique_ptr<A> ptr(new A);
D& test = dynamic_cast<D&>(*ptr.get()); // 向下转型失败, 会 throw
std::cout << ptr.get() << std::endl;
std::cout << &test << std::endl;
} catch (const std::bad_cast& e) {
std::cout << e.what() << '\n';
}
}

// Possible output:
// downcast from b2 to d successful
// 0xee1be0
// 0xee1be0
// std::bad_cast

const_cast

在有不同 cv 限定的类型间转换。

语法

const_cast <new_type> (expression) 返回 new_type 类型的值。

解释

唯有下列转换能用 const_cast 进行。特别是,唯有 const_cast 可用于转型掉(移除)常量性或易变性。

  1. 两个指向同一类型的可能多级的指针可以互相转换,无关乎每个层级的 cv 限定符。

  2. 任何 T 类型的左值可转换为到同一类型 T 的左值或右值引用,cv 限定可更多或更少。同样地,类类型纯右值或任何类型的亡值可转换成具有更多或更少 cv 限定的右值引用。引用 const_cast 的结果指代原对象,expression 是泛左值,否则指代实质化的临时量 (C++17 起)

  3. 同样的规则适用于可能多层的到数据成员的指针,及可能多层的到已知和未知边界数组(cv 限定元素的数组被认为是自身亦有 cv 限定) (C++17 起)

  4. 空指针值可转换成 new_type 的空指针值

同所有转型表达式,结果是:

  • 左值,若 new_type 是左值引用或到函数类型的右值引用;
  • 亡值,若 new_type 是到对象类型的右值引用;
  • 否则为纯右值。

函数指针和成员函数指针不可用于 const_cast

const_cast 使得能够组成实际指代 const 对象 的到非 const 类型的引用或指针,或组成实际指代 volatile 对象的到非 volatile 类型的引用或指针。通过非 const 访问路径修改 const 对象和通过非 volatile 泛左值涉指 volatile 对象是未定义行为。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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;
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; // 未定义行为

void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
// const_cast<void(type::*)(int)>(pmf); // 编译错误:const_cast 不可用于成员函数指针
}

reinterpret_cast

通过重新解释底层位模式在类型间转换。

语法

reinterpret_cast <new_type> (expression) 返回 new_type 类型的值。

解释

static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖其类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将 expression 视为如同具有 new_type 类型一样处理。

唯有下列转换能用 reinterpret_cast 进行,但若转换会转型走常量性易变性则亦不允许

  1. 整型、枚举、指针或成员指针类型的表达式可转换到其自身的类型。产生的值与 表达式 的相同。(C++11 起)

  2. 指针能转换成大小足以保有其类型所有值的任何整型类型(例如转换成 std::uintptr_t)

  3. 任何整型或枚举类型的值可转换到指针类型。指针转换到有足够大小的整数再转换回同一指针类型后,保证拥有其原值,否则结果指针无法安全地解引用(不保证相反方向的往返转换;相同指针可拥有多种整数表示)。不保证空指针常量 NULL 或整数零生成目标类型的空指针值;为此目的应该用 static_cast 或 隐式转换。

  4. 任何 std::nullptr_t 类型的值,包括 nullptr,可转换成任何整型类型,如同它是 (void*)0 一样,但没有值能转换成 std::nullptr_t,甚至 nullptr 也不行:为该目的应该用 static_cast。(C++11 起)

  5. 任何对象指针类型 T1* 可转换成指向对象指针类型 cv T2 。这严格等价于 static_cast<cv T2*>(static_cast<cv void*>(表达式))(这意味着,若 T2 的对齐要求不比 T1 的更严格,则指针值不改变,且将结果指针转换回原类型将生成其原值)。任何情况下,只有类型别名化(type aliasing)规则(见此)允许时,结果指针才可以安全地解引用。

  6. T1 类型的左值表达式可转换成到另一个类型 T2 的引用。结果是与原左值指代同一对象,但有不同类型的左值或亡值。不创建临时量,不进行复制,不调用构造函数或转换函数。只有类型别名化(type aliasing)规则(见此)允许时,结果指针才可以安全地解引用。

  7. 任何函数指针可转换成指向不同函数类型的指针。通过指向不同函数类型的指针调用函数是未定义的,但将这种指针转换回指向原函数类型的指针将生成指向原函数的指针值。

  8. 一些实现上(特别是在任何 POSIX 兼容的系统上,即基于 dlsym 的要求),函数指针可以转换成 void* 或任何其他对象指针,反之亦然。若实现支持双向的转换,则转换回原类型将生成原值,否则结果指针不能安全地解引用或调用。

  9. 任何指针类型的空指针值可转换成任何其他指针类型,产生该类型的空指针值。注意不能用 reinterpret_cast 将空指针常量 nullptr 或任何其他 std::nullptr_t 类型的值转换为指针:为此目的应该使用隐式转换或 static_cast

  10. 成员函数指针可转换成指向不同类型的不同成员函数的指针。转换回原类型将生成原值,否则结果指针不能安全使用。

  11. 指向某类 T1 的成员对象的指针可转换成指向另一个类 T2 的另一个成员对象的指针。若 T2 的对齐不比 T1 更严格,则转换回原类型 T1 将生成原值,否则不能安全地使用结果指针。

同所有转型表达式,结果是:

  • 左值,若 new_type 是左值引用或到函数类型的右值引用;
  • 亡值,若 new_type 是到对象类型的右值引用;
  • 否则为纯右值。

参考

static_cast

dynamic_cast

const_cast

reinterpret_cast

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×