什么是正确的用途:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C型演员表
(type)value
- 函数式强制转换
type(value)
如何决定在哪些特定情况下使用哪个?
static_cast
是您应该尝试使用的第一个演员表。它执行诸如类型之间的隐式转换(例如int
tofloat
或指向 的指针void*
)之类的事情,它还可以调用显式转换函数(或隐式转换函数)。在许多情况下,static_cast
不需要显式声明,但重要的是要注意T(something)
语法等效于(T)something
并且应该避免(稍后会详细介绍)。然而, AT(something, something_else)
是安全的,并保证调用构造函数。
static_cast
也可以通过继承层次结构进行转换。向上转换(朝向基类)时没有必要,但向下转换时,只要不通过virtual
继承进行转换,就可以使用它。但是,它不进行检查,并且将static_cast
层次结构向下到实际上不是对象类型的类型是未定义的行为。
const_cast
可用于删除或添加const
到变量;没有其他 C++ 演员能够删除它(甚至没有reinterpret_cast
)。需要注意的是,只有当原始变量是;时,修改以前的const
值才是未定义的。const
如果您使用它来取消对const
未声明的内容的引用const
,则它是安全的。例如,这在基于 重载成员函数时很有用const
。它也可以用来添加const
到一个对象中,比如调用一个成员函数的重载。
const_cast
也同样适用于volatile
,尽管这种情况不太常见。
dynamic_cast
专门用于处理多态性。您可以将指向任何多态类型的指针或引用转换为任何其他类类型(多态类型至少有一个声明或继承的虚函数)。您不仅可以将其用于向下投射 - 您还可以向侧面投射,甚至可以向上投射另一条链。将dynamic_cast
寻找所需的对象并在可能的情况下返回它。如果不能,它会nullptr
在指针的情况下返回,或者在引用的情况下抛出std::bad_cast
。
dynamic_cast
但是有一些限制。virtual
如果继承层次结构中有多个相同类型的对象(所谓的“可怕的菱形”)并且您没有使用继承,则它不起作用。它也只能通过公共继承——它总是无法通过protected
或private
继承。然而,这很少成为问题,因为这种继承形式很少见。
reinterpret_cast
是最危险的演员表,应该非常谨慎地使用。它将一种类型直接转换为另一种类型——例如将值从一个指针转换为另一种,或将指针存储在 中int
,或各种其他令人讨厌的事情。很大程度上,您获得的唯一保证reinterpret_cast
是,通常如果您将结果转换回原始类型,您将获得完全相同的值(但如果中间类型小于原始类型,则不会)。也有许多reinterpret_cast
不能做的转换。它主要用于特别奇怪的转换和位操作,例如将原始数据流转换为实际数据,或将数据存储在指向对齐数据的指针的低位中。
C 风格转换和函数风格转换分别使用(type)object
or进行转换type(object)
,并且在功能上是等价的。它们被定义为以下成功的第一个:
const_cast
static_cast
(虽然忽略了访问限制)static_cast
(见上文),然后const_cast
reinterpret_cast
reinterpret_cast
, 然后const_cast
因此,在某些情况下,它可以用作其他类型转换的替代品,但由于能够转化为 a reinterpret_cast
,因此可能非常危险,并且在需要显式转换时应该首选后者,除非您确定static_cast
会成功或reinterpret_cast
会失败. 即便如此,考虑更长、更明确的选项。
C 风格的强制转换在执行 a 时也会忽略访问控制static_cast
,这意味着它们能够执行其他强制转换无法执行的操作。不过,这主要是一个杂物,在我看来,这只是避免 C 风格转换的另一个原因。
用于dynamic_cast
在继承层次结构中转换指针/引用。
用于static_cast
普通类型转换。
用于reinterpret_cast
位模式的低级重新解释。使用时要格外小心。
用于const_cast
抛掷const/volatile
。避免这种情况,除非您使用 const 不正确的 API 被卡住。
(上面已经给出了很多理论和概念上的解释)
以下是我使用static_cast、dynamic_cast、const_cast、reinterpret_cast时的一些实际示例。
(也参考这个来理解解释:http ://www.cplusplus.com/doc/tutorial/typecasting/ )
静态转换:
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
动态转换:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast :
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast :
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
如果您了解一点内部知识可能会有所帮助...
static_cast
float
C++ 编译器已经知道如何在缩放器类型之间进行转换,例如int
. 为他们使用static_cast
。A
为 时B
,static_cast
调用作为参数B
传递的构造函数。A
或者,A
可以有一个转换运算符(即A::operator B()
)。如果B
没有这样的构造函数,或者A
没有转换运算符,则会出现编译时错误。A*
转换到总是成功,否则会出现编译错误。B*
A&
to B&
。dynamic_cast
(Base*)
to可能会失败。(Derived*)
A*
to B*
,如果 cast 无效,则 dynamic_cast 将返回 nullptr。A&
转换B&
无效,则 dynamic_cast 将抛出 bad_cast 异常。const_cast
set<T>
它只返回它的元素作为 const 以确保你不改变它的键。但是,如果您的意图是修改对象的非关键成员,那么应该没问题。您可以使用 const_cast 删除 constness。T& SomeClass::foo()
以及const T& SomeClass::foo() const
. 为避免代码重复,您可以应用 const_cast 从另一个函数返回一个函数的值。reinterpret_cast
float
到 4 字节int
来查看位的float
样子。除了到目前为止的其他答案之外,这里还有一个不明显的例子,其中static_cast
还不够,所以reinterpret_cast
需要这样做。假设有一个函数在输出参数中返回指向不同类(不共享公共基类)对象的指针。这种函数的一个真实例子是CoCreateInstance()
(参见最后一个参数,实际上是void**
)。假设您从该函数请求特定类的对象,因此您事先知道指针的类型(您经常为 COM 对象执行此操作)。在这种情况下,您不能将指向您的指针的指针转换为void**
with static_cast
:您需要reinterpret_cast<void**>(&yourPointer)
.
在代码中:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
但是,static_cast
适用于简单指针(不是指向指针的指针),因此可以重写上述代码以避免reinterpret_cast
(以额外变量为代价)以下列方式:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
static_cast
vs dynamic_cast
vsreinterpret_cast
内部视图上的向下/向上
在这个答案中,我想在一个具体的向上/向下转换示例中比较这三种机制,并分析底层指针/内存/程序集发生了什么,以具体了解它们的比较方式。
我相信这将对这些演员的不同之处提供一个很好的直觉:
static_cast
:在运行时进行一个地址偏移(运行时影响低)并且没有安全检查向下转换是否正确。
dyanamic_cast
: 在运行时执行相同的地址偏移static_cast
,但也会使用 RTTI 进行昂贵的安全检查,以确保向下转换是正确的。
nullptr
此安全检查允许您通过检查指示无效向下转换的返回来查询基类指针是否在运行时属于给定类型。
因此,如果您的代码无法检查nullptr
并采取有效的非中止操作,您应该只使用static_cast
而不是动态转换。
如果中止是您的代码可以执行的唯一操作,也许您只想启用dynamic_cast
调试构建 ( -NDEBUG
),并使用static_cast
其他方式,例如,如此处所做的,以不减慢您的快速运行。
reinterpret_cast
: 在运行时什么都不做,甚至地址偏移也不做。指针必须准确地指向正确的类型,甚至基类都不起作用。除非涉及原始字节流,否则您通常不希望这样做。
考虑以下代码示例:
主文件
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
编译、运行和反汇编:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
wheresetarch
用于禁用 ASLR,以便更轻松地比较运行。
可能的输出:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
现在,正如在:https ://en.wikipedia.org/wiki/Virtual_method_table中提到的,为了有效地支持虚方法调用,假设 B1 的内存数据结构的形式为:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
并B2
具有以下形式:
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
那么内存数据结构D
必须看起来像:
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
关键事实是 的内存数据结构D
在其内部包含与B1
and相同的内存结构B2
,即:
int_in_b1
int_in_b2
因此,我们得出关键结论:
向上转换或向下转换只需将指针值移动编译时已知的值
这样,当D
被传递给基本类型数组时,类型转换实际上会计算偏移量并指向看起来与B2
内存中的有效值完全一样的东西,除了这个有 vtableD
而不是B2
,因此所有虚拟调用都透明地工作。
例如:
b2s[1] = &d;
只需要获取d
+8的地址就可以到达对应的类B2数据结构。
现在,我们终于可以回到类型转换和具体示例的分析上了。
从标准输出输出我们看到:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
因此,在那里进行的隐式static_cast
计算确实正确计算了从 0x7fffffffc930 处的完整D
数据结构到 0x7fffffffc940 处的B2
类似结构的偏移量。我们还推断,介于 0x7fffffffc930 和 0x7fffffffc940 之间的可能是B1
数据和 vtable。
然后,在 downcast 部分,现在很容易理解无效部分如何失败以及为什么:
static_cast<D*>(b2s[0]) 0x7fffffffc910
:编译器刚刚在编译时上升 0x10 字节以尝试从 aB2
转到包含D
但是因为b2s[0]
不是 a D
,所以它现在指向一个未定义的内存区域。
拆解是:
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
所以我们看到 GCC 确实:
D
不存在的dynamic_cast<D*>(b2s[0]) 0
: C++居然发现强制转换无效并返回nullptr
!
这不可能在编译时完成,我们将从反汇编中确认:
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
首先进行 NULL 检查,如果输入为 NULL,则返回 NULL。
否则,它会在 RDX、RSI 和 RDI 中设置一些参数并调用__dynamic_cast
.
我现在没有耐心进一步分析这一点,但正如其他人所说,唯一可行的方法是__dynamic_cast
访问一些表示类层次结构的额外 RTTI 内存数据结构。
因此,它必须从该B2
表的条目开始,然后遍历该类层次结构,直到找到用于D
类型转换的 vtable 来自b2s[0]
.
这就是为什么动态转换可能很昂贵的原因!这是一个示例,其中在复杂项目中将 a转换为 a的单行补丁将运行时间减少了 33%!dynamic_cast
static_cast
.
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
这个只是盲目相信我们:我们说有一个D
at address b2s[1]
,编译器不做偏移计算。
但这是错误的,因为D实际上在0x7fffffffc930,0x7fffffffc940是D内部的类似B2的结构!所以垃圾被访问了。
我们可以从-O0
只是移动值的可怕程序集来确认这一点:
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
相关问题:
在 Ubuntu 18.04 amd64、GCC 7.4.0 上测试。
虽然其他答案很好地描述了 C++ 强制转换之间的所有差异,但我想添加一个简短说明,为什么您不应该使用 C 样式强制转换(Type) var
和Type(var)
.
对于 C++ 初学者来说,C 样式转换看起来像是 C++ 转换(static_cast<>()、dynamic_cast<>()、const_cast<>()、reinterpret_cast<>())的超集操作,有人可能更喜欢它们而不是 C++ 转换. 事实上,C 风格的演员表是超集并且写起来更短。
C 风格转换的主要问题是它们隐藏了开发人员转换的真实意图。C 风格的转换几乎可以执行所有类型的转换,从由 static_cast<>() 和 dynamic_cast<>() 完成的正常安全转换到像 const_cast<>() 这样的潜在危险转换,其中 const 修饰符可以被删除,因此 const 变量可以修改和 reinterpret_cast<>() 甚至可以将整数值重新解释为指针。
这是示例。
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
将 C++ 强制转换添加到语言中的主要原因是允许开发人员阐明他的意图——他为什么要进行这种强制转换。通过使用在 C++ 中完全有效的 C 样式转换,您的代码可读性降低并且更容易出错,特别是对于没有创建您的代码的其他开发人员而言。因此,为了使您的代码更具可读性和明确性,您应该始终更喜欢 C++ 强制转换而不是 C 样式强制转换。
这是 Bjarne Stroustrup(C++ 的作者)的书 The C++ Programming Language 4th edition - page 302 的简短引述。
这种 C 风格的转换比命名转换运算符危险得多,因为这种符号在大型程序中更难发现,而且程序员想要的转换类型并不明确。
为了理解,让我们考虑下面的代码片段:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
只有第 (4) 行编译没有错误。只有reinterpret_cast可用于将指向对象的指针转换为指向任何不相关对象类型的指针。
需要注意的一点是:dynamic_cast会在运行时失败,但是在大多数编译器上它也将无法编译,因为被强制转换的指针的结构中没有虚函数,这意味着dynamic_cast仅适用于多态类指针.
何时使用 C++ cast: