的正确用法是:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C型演员表
(type)value
- 功能样式转换
type(value)
如何决定在特定情况下使用哪个?
的正确用法是:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
如何决定在特定情况下使用哪个?
Answers:
static_cast
是您应该尝试使用的第一个演员表。它可以进行类型之间的隐式转换(例如int
to float
或指向的指针void*
),还可以调用显式转换函数(或隐式函数)。在许多情况下,static_cast
没有必要明确声明,但必须注意,T(something)
语法(T)something
与该语法等效,应避免使用(稍后会详细介绍)。T(something, something_else)
但是,A 是安全的,并且保证可以调用构造函数。
static_cast
也可以遍历继承层次结构。向上(朝向基类)进行强制转换时没有必要,但是向下进行转换时,只要不通过virtual
继承进行转换就可以使用。但是,它不进行检查,static_cast
将层次结构划分为实际上不是对象类型的类型是一种未定义的行为。
const_cast
可用于删除或添加const
到变量;没有其他C ++ cast能够删除它(甚至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
或进行的转换type(object)
,并且在功能上等效。它们被定义为以下成功的第一个:
const_cast
static_cast
(尽管忽略访问限制)static_cast
(请参见上文),然后 const_cast
reinterpret_cast
reinterpret_cast
, 然后 const_cast
因此,它可以在某些情况下替代其他类型的转换,但是由于有能力降级为reinterpret_cast
,因此非常危险,并且在需要显式转换时应首选后者,除非您确定static_cast
会成功还是reinterpret_cast
会失败。即使这样,也可以考虑更长,更明确的选项。
C样式转换在执行时也会忽略访问控制static_cast
,这意味着它们具有执行其他转换无法执行的操作的能力。但是,这主要是一种争执,在我看来,这是避免使用C样式强制转换的另一个原因。
const
(甚至无法删除reinterpret_cast
)” ...真的吗?那reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
呢
reinterpret_cast
是,在处理API的一组不透明数据类型时,这通常是选择的武器
使用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/)
static_cast:
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);
.....
}
dynamic_cast:
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<char*>(&val)
吗?
static_cast
仅在定义了转换的类型之间,通过继承可见的关系或到/ from之间起作用void *
。除了其他方面,还有其他演员。允许reinterpret cast
任何char *
类型的参数读取任何对象的表示-这是该关键字有用的唯一情况之一,而不是猖implementation的实现/未定义行为的生成器。但这不被视为“正常”转换,因此(通常)非常保守的人不允许这样做static_cast
。
如果您不了解内部原理,可能会有所帮助...
static_cast
static_cast
他们。A
到B
,static_cast
调用B
的构造函数传递A
的PARAM。或者,A
可以有一个转换运算符(即A::operator B()
)。如果B
没有这样的构造函数,或者A
没有转换运算符,则会出现编译时错误。A*
为B*
总是成功,否则会出现编译错误。A&
于B&
。dynamic_cast
(Base*)
to (Derived*)
可能会失败。A*
to B*
,如果强制转换无效,则dynamic_cast将返回nullptr。A&
要B&
投的是无效的,那么将dynamic_cast会抛出bad_cast异常。const_cast
set<T>
,例如,该容器仅将其元素返回为const以确保您不更改其键。但是,如果您打算修改对象的非关键成员,那么应该可以。您可以使用const_cast删除常数。T& SomeClass::foo()
和const T& SomeClass::foo() const
。为了避免代码重复,可以将const_cast应用于从另一个函数返回的值。reinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
您将获得UB,如果幸运的话,它可能会在运行时导致段错误。2.动态铸造也可以用于交叉铸造。3.在某些情况下,const转换可能导致UB。使用mutable
可能是实现逻辑常数的更好选择。
mutable
,跨铸造等
请问这个回答你的问题?
我从未使用过reinterpret_cast
,并且想知道是否遇到需要它的箱子是否散发出不良的设计味道。在我研究的代码库中,dynamic_cast
使用了很多代码。与之不同的 static_cast
是,dynamic_cast
运行时检查可能是您想要的(更安全)或可能不是(更多开销)(请参阅msdn)。
reinterpret_cast
了从数组中提取数据的方法。例如,如果我有一个char*
很大的缓冲区,里面装满了打包的二进制数据,则需要遍历并获取不同类型的单个基元。像这样的东西:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
,没有太多用途。
reinterpret_cast
出于一个原因。我已经看到原始对象数据存储到数据库中的“ blob”数据类型,然后当从数据库中检索数据时,该数据reinterpret_cast
用于将这些原始数据转换为对象。
除了到目前为止的其他答案之外,这是一个不明显的示例,其中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<void*>(pNetFwPolicy2)
,而不是static_cast<void**>(&pNetFwPolicy2)
?
尽管其他答案很好地描述了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变量可以被修改并重新解释_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 ++的作者)《 C ++编程语言》第4版-第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:
static_cast
向下/向上显示vs dynamic_cast
vs reinterpret_cast
内部视图
在这个答案中,我想在一个具体的向上/向下转换示例中比较这三种机制,并分析底层指针/内存/程序集发生了什么,以对它们如何进行比较有一个具体的了解。
我相信这将很好地说明这些演员的不同之处:
static_cast
:在运行时进行一个地址偏移(对运行时的影响较小),并且没有安全检查向下转换是否正确。
dyanamic_cast
:在运行时像一样执行相同的地址偏移量static_cast
,但也使用RTTI进行昂贵的安全检查以确保下行正确。
通过此安全检查,您可以在运行时通过检查基类指针是否为给定类型来检查其返回是否nullptr
指示无效的向下转换,从而查询该类型。
因此,如果您的代码无法进行检查nullptr
并采取有效的非中止操作,则应使用static_cast
而不是动态强制转换。
如果中止是您的代码可以采取的唯一措施,则也许您只想启用dynamic_cast
in debug builds(-NDEBUG
),然后使用static_cast
其他方法(例如,如此处所做的操作),以免减慢快速运行的速度。
reinterpret_cast
:在运行时不执行任何操作,甚至不执行地址偏移量。指针必须精确地指向正确的类型,甚至基类也不能工作。除非涉及原始字节流,否则通常不希望这样做。
考虑以下代码示例:
main.cpp
#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
在那里setarch
被用来禁用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所述,为了有效地支持虚拟方法调用,其内存数据结构D
必须类似于:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
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
和B2
内部兼容的存储结构。
因此,我们得出了关键的结论:
向上或向下转换只需要将指针值移动编译时已知的值
这样,当D
传递给基本类型数组时,类型转换实际上会计算该偏移量,并指向看起来与B2
内存中的有效值完全相同的内容:
b2s[1] = &d;
除了这个有一个vtable D
而不是B2
,因此所有虚拟调用都是透明的。
现在,我们终于可以返回类型转换并分析我们的具体示例。
从stdout输出中,我们看到:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
因此,static_cast
此处的隐式操作正确地计算了从D
0x7fffffffcc930 的完整数据结构到0x7fffffffc940处的偏移量B2
。我们还推断出介于0x7fffffffc930和0x7fffffffc940之间的可能是B1
数据和vtable。
然后,在垂头丧气的部分,现在很容易理解无效的失败方式以及原因:
static_cast<D*>(b2s[0]) 0x7fffffffc910
:编译器在编译时字节刚上升0x10时尝试从a B2
转到包含D
但是由于b2s[0]
不是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 dynamic_cast
可以static_cast
将运行时间减少33%!。
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
这个只是盲目地相信我们:我们说有一个D
at地址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上测试。