什么是正确的用途:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value (C 风格转换)
type(value) (函数式转换)
如何决定在哪些特定情况下使用哪个?
static_cast
是您应该尝试使用的第一个演员表。它执行诸如类型之间的隐式转换(例如 int
到 float
或指向 void*
的指针)之类的事情,它还可以调用显式转换函数(或隐式转换函数)。在许多情况下,不需要显式声明 static_cast
,但重要的是要注意 T(something)
语法等效于 (T)something
并且应该避免使用(稍后会详细介绍)。然而,T(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-style cast 和 function-style cast 分别是使用 (type)object
或 type(object)
进行的强制转换,并且在功能上是等效的。它们被定义为以下成功的第一个:
const_cast
static_cast(尽管忽略了访问限制)
static_cast(见上文),然后是 const_cast
reinterpret_cast
重新解释_cast,然后是 const_cast
因此,在某些情况下,它可以用作其他类型转换的替代品,但由于能够演变为 reinterpret_cast
,因此非常危险,并且在需要显式转换时应该首选后者,除非您确定 {2 } 将成功或 reinterpret_cast
将失败。即便如此,考虑更长、更明确的选项。
C 风格的转换在执行 static_cast
时也会忽略访问控制,这意味着它们能够执行其他转换无法执行的操作。不过,这主要是一个杂物,在我看来,这只是避免 C 风格转换的另一个原因。
使用 dynamic_cast 在继承层次结构中转换指针/引用。
使用 static_cast 进行普通类型转换。
使用 reinterpret_cast 对位模式进行低级重新解释。使用时要格外小心。
使用 const_cast 来抛弃 const/volatile。避免这种情况,除非您使用 const 不正确的 API 被卡住。
(上面已经给出了很多理论和概念上的解释)
下面是我使用 static_cast、dynamic_cast、const_cast、reinterpret_cast 时的一些实际示例。
(也可以参考this来理解解释: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<char*>(&val)
不一样吗?
static_cast
仅适用于具有已定义转换、继承可见关系或到/来自 void *
的类型。对于其他一切,还有其他演员表。 reinterpret cast
到任何 char *
类型都允许读取任何对象的表示 - 这是该关键字有用的唯一情况之一,而不是实现/未定义行为的猖獗生成器。但这不被视为“正常”转换,因此(通常)非常保守的 static_cast
不允许这样做。
EventData
对象的指针,仅此而已)。不幸的是,我认为没有任何实用的方法可以以任何有意义的方式对 void 指针进行类型检查。理想情况下,参数将是强类型的。只是一些观察;不是对答案的批评。
如果您了解一点内部知识可能会有所帮助...
static_cast
C++ 编译器已经知道如何在定标器类型(例如 float 到 int)之间进行转换。为他们使用 static_cast。
当您要求编译器从类型 A 转换为 B 时,static_cast 调用 B 的构造函数,将 A 作为参数传递。或者,A 可以有一个转换运算符(即 A::operator B())。如果 B 没有这样的构造函数,或者 A 没有转换运算符,则会出现编译时错误。
如果 A 和 B 在继承层次结构(或 void)中,则从 A* 转换为 B* 始终成功,否则会出现编译错误。
问题:如果将基指针转换为派生指针,但如果实际对象不是真正的派生类型,则不会出错。您会得到错误的指针,并且很可能在运行时出现段错误。 A& 到 B& 也是如此。
问题:从 Derived 转换为 Base 或反之亦然创建新副本!对于来自 C#/Java 的人来说,这可能是一个巨大的惊喜,因为结果基本上是从 Derived 创建的一个切掉的对象。
dynamic_cast
dynamic_cast 使用运行时类型信息来确定强制转换是否有效。例如,如果指针实际上不是派生类型,则 (Base*) 到 (Derived*) 可能会失败。
这意味着,与 static_cast 相比,dynamic_cast 非常昂贵!
对于 A* 到 B*,如果转换无效,则 dynamic_cast 将返回 nullptr。
对于 A& 到 B&,如果转换无效,则 dynamic_cast 将抛出 bad_cast 异常。
与其他强制转换不同,存在运行时开销。
const_cast
虽然 static_cast 可以对 const 做非常量,但它不能反过来。 const_cast 可以做这两种方式。
这很方便的一个例子是遍历像 set
另一个例子是当你想实现 T& SomeClass::foo() 以及 const T& SomeClass::foo() const 时。为避免代码重复,您可以应用 const_cast 从另一个函数返回一个函数的值。
reinterpret_cast
这基本上说在这个内存位置获取这些字节并将其视为给定对象。
例如,您可以将 4 字节的 float 加载到 4 字节的 int 中,以查看 float 中的位是什么样子。
显然,如果数据类型不正确,您可能会遇到段错误。
此演员表没有运行时开销。
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. 常量转换在某些情况下会导致 UB。使用 mutable
可能是实现逻辑常量的更好选择。
mutable
带来的所有其他复杂情况、交叉施法等让他们不知所措。
this 是否回答了您的问题?
我从未使用过 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 对象执行此操作)。在这种情况下,您不能使用 static_cast
将指向您的指针的指针转换为 void**
:您需要 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)
那样工作吗?
static_cast
vs dynamic_cast
vs reinterpret_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
其中 setarch
是 used to disable 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
和B2
相同的内存结构,即:
+0 看起来与 B1 完全一样,其中 D 的 B1 vtable 后跟 int_in_b1
+8 看起来与 B2 完全一样,其中 D 的 B2 vtable 后跟 int_in_b2
因此,我们得出关键结论:
向上转换或向下转换只需将指针值移动编译时已知的值
这样,当 D
被传递到基本类型数组时,类型转换实际上会计算该偏移量并指向内存中看起来与有效 B2
完全相同的东西,除了这个具有 D
的 vtable 而不是B2
,因此所有虚拟调用都是透明的。
例如:
b2s[1] = &d;
只需要获取d
+ 8的地址就可以到达对应的类B2数据结构。
现在,我们终于可以回到类型转换和具体示例的分析上了。
从标准输出输出我们看到:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
因此,在那里完成的隐式 static_cast
确实正确计算了从 0x7fffffffc930 处的完整 D
数据结构到 B2
的偏移量,例如 0x7fffffffc940 处的偏移量。我们还推断,位于 0x7fffffffc930 和 0x7fffffffc940 之间的可能是 B1
数据和 vtable。
然后,在 downcast 部分,现在很容易理解无效部分如何失败以及为什么:
static_cast
检查指针是否为NULL,如果是则返回NULL
否则,减去 0x10 得到不存在的 D
dynamic_cast
reinterpret_cast
相关问题:
什么时候应该使用 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?
dynamic_cast 是如何实现的
在 C++ 中使用“static_cast”进行向下转换
在 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++ 强制转换:
使用 static_cast 作为进行值转换的 C 风格强制转换的等价物,或者当我们需要将指针从类显式向上转换到其超类时。
使用 const_cast 删除 const 限定符。
使用 reinterpret_cast 将指针类型与整数和其他指针类型进行不安全的转换。仅当我们知道我们在做什么并且我们了解混叠问题时才使用它。
reinterpret_cast
的一个很好的特性,在其他答案中没有提到,它允许我们为函数类型创建一种 void*
指针。通常,对于对象类型,使用 static_cast
来检索存储在 void*
中的指针的原始类型:
int i = 13;
void *p = &i;
auto *pi = static_cast<int*>(p);
对于函数,我们必须使用 reinterpret_cast
两次:
#include<iostream>
using any_fcn_ptr_t = void(*)();
void print(int i)
{
std::cout << i <<std::endl;
}
int main()
{
//Create type-erased pointer to function:
auto any_ptr = reinterpret_cast<any_fcn_ptr_t>(&print);
//Retrieve the original pointer:
auto ptr = reinterpret_cast< void(*)(int) >(any_ptr);
ptr(7);
}
使用 reinterpret_cast
,我们甚至可以获得一个类似的 sort-of-void* 指针,用于指向成员函数的指针。
与普通的 void*
和 static_cast
一样,C++ 保证 ptr
指向 print
函数(只要我们将正确的类型传递给 reinterpret_cast
)。
const
(甚至reinterpret_cast
)”......真的吗?reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
呢?reinterpret_cast
通常是首选武器