C++中常用的四种类型转换方式
一、相关概念
C++中常用的四种类型转换方式如下:
-
静态转换(static_cast)。静态转换可以在编译时期完成,适用于具有继承关系的类或者指针类型之间的转换。静态转换会进行一些类型检查,但是不会进行运行时检查。如果类型无法转换,则会编译报错。
示例代码:
double d = 3.14; int i = static_cast<int>(d);
-
动态转换(dynamic_cast)。动态转换可以在运行时期完成,适用于具有继承关系的类或指针类型之间的转换。动态转换会进行类型检查,如果类型无法转换,则返回空指针(对于指针类型)或抛出bad_cast异常(对于引用类型)。
示例代码:
class Base { virtual void foo() {} }; class Derived : public Base {}; Base* ptr = new Derived; Derived* dptr = dynamic_cast<Derived*>(ptr); if (dptr != nullptr) { // 转换成功 } else { // 转换失败 }
-
常量转换(const_cast)。常量转换用于去掉表达式的const属性,使其变成非常量表达式。常量转换不能改变表达式的类型,只能改变const属性。
示例代码:
const int a = 10; int b = const_cast<int&>(a);
-
重解释转换(reinterpret_cast)。重解释转换用于进行各种类型之间的强制转换,包括指针、引用、整数之间的转换。它是一种非常危险的类型转换,因为它会改变数据的原本含义。
示例代码:
int a = 10; double b = reinterpret_cast<double&>(a);
二、static_cast 转换
使用隐式转换和用户定义转换的组合在类型之间进行转换。
语法:
static_cast< new-type expression>(...)
返回类型为“new-type”的值。
2.1、说明
只有以下转换可以使用static_cast完成,除非此类转换会抛弃恒定性或波动性。
(1)如果 new-type 是对某个类的引用,expression 是其非虚基的左值;或者 new-type 是指向某个完整类的指针,expression 是指向其非虚基的 prvalue 指针,static_cast执行向下转换。(如果此向下转换是不明确的、无法访问的或虚拟基础(或虚拟基础的基础结构)的,则格式不正确)。
这种向下转换不会进行运行时检查以确保对象的运行时类型是真实的,并且只有在通过其他方式保证此前提条件时才能安全使用,例如在实现静态多态性时。安全的下落可以通过dynamic_cast来完成。 如果对象表达式引用或指向的实际上是类型对象的基类子对象,则结果引用类型的封闭对象。否则,行为是未定义的。
struct B {};
struct D : B { B b; };
D d;
B& br1 = d;
B& br2 = d.b;
static_cast<D&>(br1); // OK: 左值表示原始的d对象
static_cast<D&>(br2); // UB: 子对象b不是基类子对象
(2)如果new-type是右值引用类型,则static_cast将glvalue、类prvalue或数组prvalue (c++ 17之前)的任何左值(c++ 17之后)表达式的值转换为指向与该表达式相同对象的xvalue,或者指向其基子对象(取决于new-type)。
- 如果目标类型是表达式类型的不可访问或不明确的基,则程序是病态的。
- 如果表达式是位域左值,则首先将其转换为基础类型的右值。这种类型的static_cast用于在std::move中实现move语义。
(3)如果存在从表达式到new-type的隐式转换序列,或者如果重载解析直接初始化对象或new-type类型引用的重载解析会找到至少一个可行的函数,则static_cast< new-typeexpression>()返回虚拟变量Temp,就像由new-type Temp(expression)初始化一样,这可能涉及隐式转换、调用new-type的构造函数或调用用户定义的转换操作符。对于非引用的new-type, static_cast右值表达式的结果对象是直接初始化的对象。
(4)如果new-type是void类型(可能是cv限定的),则static_cast在求值后丢弃expression的值。
(5)如果存在从new-type到表达式类型的标准转换序列,该序列不包括左值到右值、数组到指针、函数到指针、空指针、空成员指针、函数指针(自c++ 17起)或布尔转换,则static_cast可以执行该隐式转换的逆操作。
(6)如果表达式到new-type的转换涉及左值到右值、数组到指针或函数到指针的转换,则可以通过static_cast显式执行。
(7)有作用域的枚举类型可以转换为整数或浮点类型。到C++20,其结果与从枚举的基础类型到目标类型的隐式转换相同。当目标类型为bool(可能是cv限定的)时,如果原始值为零,结果为false,其他所有值为true。对于其余整型,如果枚举的值可以用目标类型表示,则结果为其值,否则未指定。
(8)整数或枚举类型的值可以转换为任何完整的枚举类型。
- 如果基础类型不固定,则在表达式的值超出范围时,行为是未定义的(范围是足够大的最小位字段的所有可能值,以容纳目标枚举的所有枚举器)。
- 如果基础类型是固定的,则结果与先将原始值转换为枚举的基础类型,然后再转换为枚举类型相同。
浮点类型的值也可以转换为任何完整的枚举类型。结果与先将原始值转换为枚举的基础类型,然后再转换为枚举类型相同。
(9)浮点类型的右值可以显式地转换为任何其他浮点类型。
(10)指向某个完整类的成员的指针可以向上转换为指向其明确的、可访问的基类的成员的指针。这个static_cast不进行检查,以确保该成员实际存在于指向object.DB的运行时类型中。
(11)指向 void 的指针类型的 prvalue(可能符合 cv 条件)可以转换为指向任何对象类型的指针。
2.2、返回值
(1)与所有强制转换表达式一样,结果是:
- 如果new-type是左值引用类型或对函数类型的右值引用,则为左值(c++ 11起);
- 如果new-type是对象类型的右值引用,则使用xvalue;(自c++ 11起)。
- 否则,使用右值。
(2)两个对象一个和b在以下情况下是指针可相互转换的:
- 它们是同一对象;
- 或者一个是联合对象,另一个是该对象的非静态数据成员;
- 或者一个是标准布局类对象,另一个是该对象或该对象的任何基类子对象的第一个非静态数据成员;
- 或者存在一个对象c这样一个和c是指针可相互转换的,并且c和b是指针可相互转换的。
union U { int a; double b; } u;
void* x = &u; // x's value is "pointer to u"
double* y = static_cast<double*>(x); // y's value is "pointer to u.b"
char* z = static_cast<char*>(x); // z's value is "pointer to u"
static_cast还可用于通过执行到特定类型的函数到指针转换来消除函数重载的歧义,比如:
std::for_each(files.begin(), files.end(),
static_cast<std::ostream&(*)(std::ostream&)>(std::flush));
2.3、示例
#include <iostream>
#include <vector>
struct B
{
int m = 42;
const char* hello() const
{
return "Hello world, this is B!\n";
}
};
struct D : B
{
const char* hello() const
{
return "Hello world, this is D!\n";
}
};
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
int main()
{
// 1. static downcast
D d;
B& br = d; // upcast via implicit conversion
std::cout << "1) " << br.hello();
D& another_d = static_cast<D&>(br); // downcast
std::cout << "1) " << another_d.hello();
// 2. lvalue to xvalue
std::vector<int> v0{1,2,3};
std::vector<int> v2 = static_cast<std::vector<int>&&>(v0);
std::cout << "2) after move, v0.size() = " << v0.size() << '\n';
// 3. initializing conversion
int n = static_cast<int>(3.14);
std::cout << "3) n = " << n << '\n';
std::vector<int> v = static_cast<std::vector<int>>(10);
std::cout << "3) v.size() = " << v.size() << '\n';
// 4. discarded-value expression
static_cast<void>(v2.size());
// 5. inverse of implicit conversion
void* nv = &n;
int* ni = static_cast<int*>(nv);
std::cout << "5) *ni = " << *ni << '\n';
// 6. array-to-pointer followed by upcast
D a[10];
[[maybe_unused]]
B* dp = static_cast<B*>(a);
// 7. scoped enum to int
E e = E::TWO;
int two = static_cast<int>(e);
std::cout << "7) " << two << '\n';
// 8. int to enum, enum to another enum
E e2 = static_cast<E>(two);
[[maybe_unused]]
EU eu = static_cast<EU>(e2);
// 9. pointer to member upcast
int D::*pm = &D::m;
std::cout << "9) " << br.*static_cast<int B::*>(pm) << '\n';
// 10. void* to any type
void* voidp = &e;
//[[maybe_unused]]
std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}
输出:
1) Hello world, this is B!
1) Hello world, this is D!
2) after move, v0.size() = 0
3) n = 3
3) v.size() = 10
5) *ni = 3
7) 2
9) 42
三、const_cast 转换
在具有不同限制资格的类型之间进行转换。
语法:
const_cast< new-type expression>(...)
返回new-type类型的值。
3.1、说明
const_cast只能进行以下转换。特别是,只有const_cast可用于抛弃(去除)恒常性或波动性。
(1)指向同一类型的两个可能的多级指针可以相互转换,而不必考虑每个级别上的cv限定符。
(2)任何类型的左值都可以转换为相同类型的左值或右值引用,或多或少受cv限制。同样,类类型的右值或任何类型的右值都可以转换为或多或少限定cv的右值引用。如果表达式是一个全局值,则引用const_cast的结果指向原始对象,否则指向物化的临时对象。
(4)同样的规则也适用于可能指向数据成员的多级指针,以及可能指向具有已知和未知边界的数组的多级指针(指向限定cv的元素的数组本身也被认为是限定cv的)。
(5)空指针值可以转换为new-type的空指针值。
3.2、返回值
与所有强制转换表达式一样,结果是:
- 如果new-type是左值引用类型或对函数类型的右值引用,则为左值(c++ 11起);
- 如果new-type是对象类型的右值引用,则使用xvalue;
- 否则使用右值。
指向函数的指针和指向成员函数的指针不受const_cast约束。
const_cast可以形成指向实际引用 const 对象的非 const 类型的引用或指针,或者形成指向实际引用 volatile 对象的非易失性类型的引用或指针。通过非常量访问路径修改 const 对象并通过非 const glvalue 引用易失性对象会导致未定义的行为。
3.3、示例
#include <iostream>
struct type
{
int i;
type(): i(3) {}
void f(int v) const
{
// this->i = v; // compile error: this is a pointer to const
const_cast<type*>(this)->i = v; // OK as long as the type object isn't const
}
};
int main()
{
int i = 3; // i is not declared const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK: modifies i
std::cout << "i = " << i << '\n';
type t; // if this was const type t, then t.f(4) would be undefined behavior
t.f(4);
std::cout << "type::i = " << t.i << '\n';
const int j = 3; // j is declared const
[[maybe_unused]]
int* pj = const_cast<int*>(&j);
// *pj = 4; // undefined behavior
[[maybe_unused]]
void (type::* pmf)(int) const = &type::f; // pointer to member function
// const_cast<void(type::*)(int)>(pmf); // compile error: const_cast does
// not work on function pointers
}
结果输出:
i = 4
type::i = 4
四、dynamic_cast 转换
沿继承层次结构安全地向上、向下和横向转换对类的指针和引用。
语法:
dynamic_cast< target-type expression>(...)
参数:
- target-type:指向完整类类型的指针、对完整类类型的引用或指向(可选符合 CV 条件)void 的指针
- expression:lvalue(直到 C++11)glvalue (自 C++11) 如果目标类型是引用,则指向完整类类型的指针的 PR值(如果目标类型是指针)。
如果强制转换成功,dynamic_cast返回目标类型的值。如果强制转换失败并且目标类型是指针类型,则返回该类型的空指针。如果强制转换失败并且目标类型是引用类型,则会引发与 std::bad_cast 类型的处理程序匹配的异常。
4.1、说明
为了便于描述,“表达式或结果是对”的引用“表示”它是类型的glvalue“,遵循decltype的约定。TT
只有以下转换可以用dynamic_cast完成,除非这种转换会抛弃恒定性或波动性。
(1)如果表达式的类型恰好是目标类型或目标类型的 cv 限定度较低的版本,则结果是表达式的值,类型为 target-type。(换句话说,dynamic_cast可以用来增加恒定性。隐式转换和static_cast也可以执行此转换。
(2)如果表达式的值是空指针值,则结果是目标类型的空指针值。
(3)如果 target-type 是指向 的指针或引用,而表达式的类型是对 的指针或引用,其中 是 的唯一、可访问的基类,则结果是指向表达式指向或标识的对象内的类子对象的指针或引用。(注意:隐式转换和static_cast也可以执行此转换。
(4)如果表达式是指向多态类型的指针,而目标类型是指向 void 的指针,则结果是指向表达式指向或引用的最派生对象的指针。
(5)如果表达式是指向多态类型的指针或引用,而目标类型是指向该类型的指针或引用,则执行运行时检查:
- 检查通过表达式指向/识别的最派生对象。如果在该对象中,表达式指向/引用 的公共基,并且如果只有一个类型的对象派生自表达式指向/标识的子对象,则强制转换点的结果/引用该对象。(这被称为“向下投掷”)。
- 否则,如果表达式点/引用最派生对象的公共基,同时,派生最多的对象具有明确的公共基类类型,则强制转换点的结果/引用该(这称为“侧播”)。
- 否则,运行时检查将失败。如果在指针上使用
dynamic_cast
,则返回目标类型类型的空指针值。如果它用于引用,则会抛出异常std::bad_cast
。
(6)当 dynamic_cast 用于构造函数或析构函数(直接或间接)并且表达式引用当前正在构造/销毁的对象时,该对象被视为派生最多的对象。如果 target-type 不是指向构造函数/析构函数自己的类或其基之一的指针或引用,则行为是未定义的。
4.2、返回值
与其他强制转换表达式类似,结果为:
- 向下强制转换也可以用static_cast来执行,这样可以避免运行时检查的开销,但是只有当程序能够保证(通过其他逻辑)表达式所指向的对象是确定的时候,向下强制转换才是安全的。
- 某些形式的dynamic_cast依赖于运行时类型标识(RTTI),即关于编译程序中每个多态类的信息。编译器通常具有禁用包含此信息的选项。
4.3、示例
#include <iostream>
struct V
{
virtual void f() {} // must be polymorphic to use runtime-checked dynamic_cast
};
struct A : virtual V {};
struct B : virtual V
{
B(V* v, A* a)
{
// casts during construction (see the call in the constructor of D below)
dynamic_cast<B*>(v); // well-defined: v of type V*, V base of B, results in B*
dynamic_cast<B*>(a); // undefined behavior: a has type A*, A not a base of B
}
};
struct D : A, B
{
D() : B(static_cast<A*>(this), this) {}
};
struct Base
{
virtual ~Base() {}
};
struct Derived: Base
{
virtual void name() {}
};
int main()
{
D d; // the most derived object
A& a = d; // upcast, dynamic_cast may be used, but unnecessary
[[maybe_unused]]
D& new_d = dynamic_cast<D&>(a); // downcast
[[maybe_unused]]
B& new_b = dynamic_cast<B&>(a); // sidecast
Base* b1 = new Base;
if (Derived* d = dynamic_cast<Derived*>(b1); d != nullptr)
{
std::cout << "downcast from b1 to d successful\n";
d->name(); // safe to call
}
Base* b2 = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b2); d != nullptr)
{
std::cout << "downcast from b2 to d successful\n";
d->name(); // safe to call
}
delete b1;
delete b2;
}
输出:
downcast from b2 to d successful
五、reinterpret_cast 转换
通过重新解释基础位模式在类型之间进行转换。
语法:
reinterpret_cast< new-type expression>(...)
返回new-type类型的值。
5.1、说明
与static_cast不同,但与const_cast一样,reinterpret_cast表达式不会编译为任何 CPU 指令(除非在整数和指针之间进行转换,或者在指针表示取决于其类型的模糊体系结构上)。它纯粹是一个编译时指令,它指示编译器将表达式视为具有 new-type 类型。
只有以下转换可以使用reinterpret_cast完成,除非此类转换会抛弃恒定性或波动性。
(1)积分、枚举、指针或指向成员的指针类型的表达式可以转换为其自己的类型。结果值与表达式的值相同。
(2)指针可以转换为任何足够大的整数类型,以容纳其类型的所有值(例如,转换为 std::uintptr_t)
(3)任何整型或枚举类型的值都可以转换为指针类型。转换为足够大小的整数并返回到相同指针类型的指针保证具有其原始值,否则无法安全地取消引用生成的指针(不保证相反方向的往返转换;同一指针可能有多个整数表示形式)空指针常量 NULL 或整数零不保证产生目标类型的空指针值;为此,应使用static_cast或隐式转换。
(4)std::nullptr_t
类型的任何值,包括空PTR可以转换为任何整数类型,就好像它是(无效*)0,但没有价值,甚至没有空PTR可以转换为 std::nullptr_t
。
(5)任何对象指针类型都可以转换为另一个对象指针类型。这完全等同于T1*cv T2*static_cast<cv T2*>(static_cast<cv void*>(表达式))
。在任何情况下,只有在类型别名规则允许的情况下,才能安全地取消引用生成的指针。
(6)类型的左值(直到C++11)glvalue(自C++11)表达式可以转换为对另一种类型的引用。结果是T1T2*reinterpret_cast<T2*>(p)
那里p是指向表达式指定的对象的“指针指向”类型的指针。不创建临时函数,不创建副本,不调用构造函数或转换函数。
(7)任何指向函数的指针都可以转换为指向不同函数类型的指针。通过指向其他函数类型的指针调用函数是未定义的,但将此类指针转换回指向原始函数类型的指针会生成指向原始函数的指针。
(8)在某些实现上,函数指针可以转换为 void*
或任何其他对象指针,反之亦然。如果实现支持双向转换,则转换为原始类型将生成原始值,否则无法取消引用或安全地调用生成的指针。
(9)任何指针类型的空指针值都可以转换为任何其他指针类型,从而生成该类型的空指针值。请注意,空指针常量空PTR或者 std::nullptr_t
类型的任何其他值不能转换为具有 reinterpret_cast
: 隐式转换的指针。
(10)指向成员函数的指针可以转换为指向不同类型的不同成员函数的指针。转换回原始类型将生成原始值,否则无法安全地使用生成的指针。
(11)指向某个类的成员对象的指针可以转换为指向另一个类的另一个成员对象的指针。如果 的对齐方式不比 '严格,则转换回原始类型将生成原始值,否则无法安全地使用生成的指针。
5.2、返回值
与所有强制转换表达式一样,结果为:
- 如果 new-type 是左值引用类型或对函数类型的右值引用,则为 lvalue(自 C++11 起);
- 如果新类型是对对象类型的右值引用,则为 x值;
- 否则为PR值。
假设满足对齐要求,则除了处理指针可相互转换对象的少数有限情况外,reinterpret_cast
不会更改指针的值:
struct S1 { int a; } s1;
struct S2 { int a; private: int b; } s2; // not standard-layout
union U { int a; double b; } u = {0};
int arr[2];
int* p1 = reinterpret_cast<int*>(&s1); // value of p1 is "pointer to s1.a" because
// s1.a and s1 are pointer-interconvertible
int* p2 = reinterpret_cast<int*>(&s2); // value of p2 is unchanged by reinterpret_cast
// and is "pointer to s2".
int* p3 = reinterpret_cast<int*>(&u); // value of p3 is "pointer to u.a":
// u.a and u are pointer-interconvertible
double* p4 = reinterpret_cast<double*>(p3); // value of p4 is "pointer to u.b": u.a and
// u.b are pointer-interconvertible because
// both are pointer-interconvertible with u
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast
// and is "pointer to arr"
5.3、示例
#include <cstdint>
#include <cassert>
#include <iostream>
int f() { return 42; }
int main()
{
int i = 7;
// pointer to integer and back
std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast is an error
std::cout << "The value of &i is " << std::showbase << std::hex << v1 << '\n';
int* p1 = reinterpret_cast<int*>(v1);
assert(p1 == &i);
// pointer to function to another and back
void(*fp1)() = reinterpret_cast<void(*)()>(f);
// fp1(); undefined behavior
int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
std::cout << std::dec << fp2() << '\n'; // safe
// type aliasing through pointer
char* p2 = reinterpret_cast<char*>(&i);
std::cout << (p2[0] == '\x7' ? "This system is little-endian\n"
: "This system is big-endian\n");
// type aliasing through reference
reinterpret_cast<unsigned int&>(i) = 42;
std::cout << i << '\n';
[[maybe_unused]] const int &const_iref = i;
// int &iref = reinterpret_cast<int&>(
// const_iref); // compiler error - can't get rid of const
// Must use const_cast instead: int &iref = const_cast<int&>(const_iref);
}
可能的输出:
The value of &i is 0x7fff352c3580
42
This system is little-endian
42
总结
-
静态类型转换是最常用的类型转换方式,它可以将一种类型的数据强制转换为另一种类型,但需要注意的是,这种转换可能会损失一些信息,因此在进行此类转换时应当谨慎。
-
动态类型转换主要用于多态类型之间的转换,它可以将基类指针或引用转换为派生类指针或引用。如果进行非法的类型转换,动态类型转换会返回一个空指针。
-
重新解释类型转换可以将一个对象的二进制表示重新解释为另一种类型的对象,这种转换通常用于底层编程和特殊的系统级程序中。
-
const_cast转换可以将const限定符添加或移除,以便在需要更改底层值时使用,但要注意的是,const_cast转换可能会导致未定义行为。