提问人:e.James 提问时间:12/2/2008 最后编辑:Mateen Ulhaqe.James 更新时间:8/26/2023 访问量:752757
什么时候应该使用 static_cast、dynamic_cast、const_cast和reinterpret_cast?
When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used?
问:
什么是正确的用途:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- (
type)value(
C 样式强制转换) type(value
) (函数样式强制转换)
如何决定在哪些特定情况下使用哪个?
答:
这回答了你的问题吗?
我从来没有用过,想知道遇到需要它的箱子是否是一种糟糕的设计。在我工作的代码库中被大量使用。与此不同的是,执行运行时检查,这可能是您想要的(更安全)也可能不是(更多开销)(请参阅 msdn)。reinterpret_cast
dynamic_cast
static_cast
dynamic_cast
评论
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
C++
C
reinterpret_cast
用于转换继承层次结构中的指针/引用。
dynamic_cast
用于普通类型转换。
static_cast
用于位模式的低级重新解释。使用时要格外小心。
reinterpret_cast
用于抛弃。避免这种情况,除非您在使用 const 不正确的 API 时遇到困难。
const_cast
const/volatile
评论
dynamic_cast
static_cast
static_cast
static_cast
是您应该尝试使用的第一个强制转换。它执行诸如类型之间的隐式转换(例如 to 或指针 to )之类的操作,还可以调用显式转换函数(或隐式转换函数)。在许多情况下,明确声明不是必需的,但重要的是要注意语法等同于并且应该避免(稍后会详细介绍)。但是,A 是安全的,并且保证可以调用构造函数。int
float
void*
static_cast
T(something)
(T)something
T(something, something_else)
static_cast
还可以通过继承层次结构进行强制转换。在向上强制转换(朝向基类)时没有必要,但是在向下强制转换时,只要不通过继承强制转换,就可以使用它。但是,它不执行检查,并且将层次结构向下到实际上不是对象类型的类型是未定义的行为。virtual
static_cast
const_cast
const_cast
可用于删除或添加到变量中;没有其他 C++ 强制转换能够删除它(甚至不能)。需要注意的是,只有当原始变量为 ;如果你用它来删除对未声明的东西的引用,它是安全的。例如,当重载基于 的成员函数时,这可能很有用。它还可用于添加到对象,例如调用成员函数重载。const
reinterpret_cast
const
const
const
const
const
const
const_cast
也同样适用于 ,尽管这不太常见。volatile
dynamic_cast
dynamic_cast
专门用于处理多态性。可以将指向任何多态类型的指针或引用强制转换为任何其他类类型(多态类型至少具有一个已声明或继承的虚函数)。您不仅可以将其用于向下投掷,还可以横向投掷,甚至可以向上投掷另一条链。将查找所需的对象,并在可能的情况下将其返回。如果不能,它将在指针的情况下返回,或者在引用的情况下抛出。dynamic_cast
nullptr
std::bad_cast
dynamic_cast
不过,有一些局限性。如果继承层次结构中有多个相同类型的对象(所谓的“可怕的钻石”),并且您没有使用继承,则它不起作用。它也只能通过公共继承——它总是无法通过或继承。然而,这很少成为问题,因为这种形式的继承很少见。virtual
protected
private
reinterpret_cast
reinterpret_cast
是最危险的演员,应该非常谨慎地使用。它直接将一种类型转换为另一种类型,例如将值从一个指针转换为另一个指针,或将指针存储在 或各种其他令人讨厌的东西中。很大程度上,您得到的唯一保证是,通常,如果将结果强制转换为原始类型,您将获得完全相同的值(但如果中间类型小于原始类型,则不会)。reinterpret_cast
也无法进行许多转换。它主要用于特别奇怪的转换和位操作,例如将原始数据流转换为实际数据,或将数据存储在指向对齐数据的指针的低位中。int
reinterpret_cast
C 型铸造和功能型铸造
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 样式强制转换在执行 时也会忽略访问控制,这意味着它们能够执行其他强制转换无法执行的操作。不过,这主要是一个问题,在我看来,这只是避免 C 型演员的另一个原因。static_cast
评论
const
reinterpret_cast
reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
reinterpret_cast
(上面已经给出了很多理论和概念上的解释)
以下是我使用 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
void *
reinterpret cast
char *
static_cast
EventData
除了到目前为止的其他答案之外,这里有一个不明显的例子,其中是不够的,所以需要。假设有一个函数,该函数在输出参数中返回指向不同类(不共享公共基类)的对象的指针。这种函数的一个真实例子是 CoCreateInstance() (
参见最后一个参数,实际上是 )。假设您从此函数请求特定类的对象,因此您事先知道指针的类型(您经常对 COM 对象执行此操作)。在这种情况下,您不能将指向指针的指针转换为:您需要。static_cast
reinterpret_cast
void**
void**
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)
如果您了解一点内部情况,可能会有所帮助......
static_cast
- C++编译器已经知道如何在缩放器类型(如to )之间进行转换。为他们使用。
float
int
static_cast
- 当您要求编译器将类型转换为 时,调用作为参数传递的构造函数。或者,可以有一个转换运算符(即 )。如果没有这样的构造函数,或者没有转换运算符,则会出现编译时错误。
A
B
static_cast
B
A
A
A::operator B()
B
A
- 如果 A 和 B 处于继承层次结构(或无效)中,则从 to 强制转换始终成功,否则会出现编译错误。
A*
B*
- 陷阱:如果您将基指针转换为派生指针,但如果实际对象不是真正的派生类型,则不会出现错误。你得到错误的指针,很可能在运行时出现段错误。这同样适用于。
A&
B&
- 陷阱:从派生到基础的施法,反之亦然,创建新的副本!对于来自 C#/Java 的人来说,这可能是一个巨大的惊喜,因为结果基本上是从 Derived 创建的一个被砍掉的对象。
dynamic_cast
- dynamic_cast使用运行时类型信息来确定强制转换是否有效。例如,如果指针实际上不是派生类型,则 to 可能会失败。
(Base*)
(Derived*)
- 这意味着,与static_cast相比,dynamic_cast非常昂贵!
- 对于 to ,如果 cast 无效,则 dynamic_cast 将返回 nullptr。
A*
B*
- 对于 to,如果强制转换无效,则dynamic_cast将抛出bad_cast异常。
A&
B&
- 与其他强制转换不同,存在运行时开销。
const_cast
- 虽然static_cast可以做非常量来控制,但它不能以其他方式进行。const_cast可以同时做两种方式。
- 这派上用场的一个例子是遍历一些容器,例如它只将其元素作为 const 返回,以确保您不会更改其键。但是,如果你的目的是修改对象的非键成员,那么它应该没问题。您可以使用const_cast删除常量。
set<T>
- 另一个例子是,当您想要实现以及 .为避免代码重复,可以将const_cast应用于从一个函数返回另一个函数的值。
T& SomeClass::foo()
const T& SomeClass::foo() const
reinterpret_cast
- 这基本上是说,在这个内存位置获取这些字节,并将其视为给定的对象。
- 例如,您可以加载 4 个字节到 4 个字节,以查看 in 中的位的外观。
float
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.
mutable
mutable
虽然其他答案很好地描述了 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++的作者)的书《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++ 强制转换:
- 使用 static_cast 作为执行值转换的 C 样式强制转换的等效项,或者当我们需要显式地将指针从类向上强制转换为其超类时。
- 使用 const_cast 删除 const 限定符。
- 使用 reinterpret_cast 可以不安全地将指针类型与整数和其他指针类型进行转换。只有当我们知道自己在做什么并且了解混叠问题时,才使用它。
评论
static_cast
vs dynamic_cast
vs reinterpret_cast
downcast/upcast 上的内部视图
在这个答案中,我想在一个具体的上行/下行示例中比较这三种机制,并分析底层指针/内存/程序集会发生什么,以具体理解它们的比较方式。
我相信这将很好地直观地了解这些演员的不同之处:
static_cast
:在运行时执行一个地址偏移(运行时影响小),并且不安全检查下行是否正确。dyanamic_cast
:在运行时执行相同的地址偏移,例如,并且还使用RTTI进行昂贵的安全检查,以确保下行是否正确。static_cast
此安全检查允许您在运行时通过检查指示无效下转换的返回值来查询基类指针是否属于给定类型。
nullptr
因此,如果您的代码无法检查这一点并执行有效的非中止操作,则应仅使用而不是动态强制转换。
nullptr
static_cast
如果中止是你的代码可以采取的唯一操作,也许你只想启用调试版本(),并使用其他方法,例如,如此处所做的那样,以免减慢你的快速运行速度。
dynamic_cast
-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
where 用于禁用 ASLR,以便更轻松地比较运行。setarch
可能的输出:
&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 完全一样,B 的 vtable 表示 D,然后是
int_in_b1
- +8 看起来与 B2 完全一样,B 的 vtable 表示 D,然后是
int_in_b2
或更高层次:
D:
+0: B1
+8: B2
+16: <fields of D itsef>
因此,我们得出了关键结论:
Upcast 或 Downcast 只需要将指针值移动一个在编译时已知的值
这样,当传递给基类型数组时,类型转换实际上会计算该偏移量,并指出一些看起来与内存中有效的内容完全相同的内容,只是这个数组具有 vtable for 而不是 ,因此所有虚拟调用都是透明的。D
B2
D
B2
例如:
b2s[1] = &d;
只需要获取地址 + 8 即可达到相应的类 B2 数据结构。d
现在,我们终于可以回到类型转换和对具体示例的分析。
从 stdout 输出中,我们看到:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
因此,在那里完成的隐式确实正确地计算了从0x7fffffffc930处的完整数据结构到0x7fffffffc940处的类似数据结构的偏移量。我们还推断,介于 0x7fffffffc930 和 0x7fffffffc940 之间的可能是数据和 vtable。static_cast
D
B2
B1
然后,在向下的部分,现在很容易理解无效部分是如何失败的以及为什么:
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 确实:
- 检查指针是否为 NULL,如果是,则返回 NULL
- 否则,从中减去0x10以达到不存在的
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 检查,如果 einput 为 NULL,则返回 NULL。
否则,它会在 RDX、RSI 和 RDI 中设置一些参数并调用 .
__dynamic_cast
我现在没有耐心进一步分析这一点,但正如其他人所说,这样做的唯一方法是访问一些额外的 RTTI 内存中数据结构,这些结构表示类层次结构。
__dynamic_cast
因此,它必须从该表的条目开始,然后遍历此类层次结构,直到它发现 类型转换的 vtable。
B2
D
b2s[0]
这就是为什么动态投射可能很昂贵的原因!下面是一个示例,在复杂项目中,将
dynamic_cast
转换为static_cast
的单衬里补丁将运行时间缩短了 33%!。reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
这个只是盲目地相信我们:我们说有一个 at 地址,编译器不进行偏移计算。D
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)
相关问题:
- 什么时候应该使用 static_cast、dynamic_cast、const_cast和reinterpret_cast?
- dynamic_cast是如何实现的
- 在C++中使用“static_cast”进行下沉
在 Ubuntu 18.04 amd64、GCC 7.4.0 上测试。
在其他答案中没有提到的一个很好的功能是,它允许我们为函数类型创建一种指针。通常,对于对象类型,用于检索存储在 :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);
}
我们甚至可以获得一个类似的 void* 指针,用于指向成员函数的指针。reinterpret_cast
与普通 和 一样,C++ 保证指向函数(只要我们将正确的类型传递给 )。void*
static_cast
ptr
print
reinterpret_cast
让我们在示例中看看 和 的区别:reinterpret_cast
static_cast
#include <iostream>
using namespace std;
class A
{
int a;
};
class B
{
int b;
};
class C : public A, public B
{
int c;
};
int main()
{
{
B b;
cout << &b << endl;
cout << static_cast<C *>(&b) << endl; // 1
cout << reinterpret_cast<C *>(&b) << endl; // 2
}
cout << endl;
{
C c;
cout << &c << endl;
cout << static_cast<B *>(&c) << endl; // 3
cout << reinterpret_cast<B *>(&c) << endl; // 4
}
cout << endl;
{
A a;
cout << &a << endl;
cout << static_cast<C *>(&a) << endl;
cout << reinterpret_cast<C *>(&a) << endl;
}
cout << endl;
{
C c;
cout << &c << endl;
cout << static_cast<A *>(&c) << endl;
cout << reinterpret_cast<A *>(&c) << endl;
}
return 0;
}
生成输出:
0x7ffcede34f0c
0x7ffcede34f08 // 1
0x7ffcede34f0c // 2
0x7ffcede34f0c
0x7ffcede34f10 // 3
0x7ffcede34f0c // 4
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
请注意,输出 和 以及 和 是不同的。为什么?在这两种情况下,其中一个是相同类型的输入,另一个是相同类型的相同输入。1
2
3
4
static_cast
reinterpret_cast
情况如下图所示:
C
包含 ,但 的起始地址与 不同。 正确计算 within 的地址。但是,返回我们作为输入给出的相同地址,这对于这种情况是不正确的:该地址没有。B
B
C
static_cast
B
C
reinterpret_cast
B
但是,在 和 指针之间转换时,两个强制转换都返回相同的结果,因为它们恰好从同一位置开始,顺便说一句,标准无论如何都不能保证这一点。A
C
我认为我们需要一个更适合初学者的解释,在我自己研究了这个主题之后,我认为我发现的最好的是这里:https://www.tutorialspoint.com/When-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used-in-Cplusplus [1]
什么时候应该在 C++ 中使用 、 和?
static_cast
dynamic_cast
const_cast
reinterpret_cast
const_cast
可用于删除或向变量添加 const。如果需要在变量中添加/删除恒定性,这可能很有用。
static_cast
这用于普通/普通类型转换。这也是负责隐式类型 co[nv]ersion 的强制转换,也可以显式调用。您应该在将 float 转换为 int、char 到 int 等情况下使用它。
dynamic_cast
此强制转换用于处理多态性。只有在强制转换为派生类时才需要使用它。这专门用于从基类转换为派生类的继承。
- 我自己补充的话:它允许安全地向下转换,从 ptr 转换为基类(通过获取从该基类继承的派生类的地址创建)到 ptr 到派生类,确保 ptr 实际上指向一个完整的、完整的派生类对象(如果不是,则返回一个)。这里的“”部分有一个很好的代码示例:https://cplusplus.com/doc/tutorial/typecasting/。(我在这里的回答中讨论了该代码)。
nullptr
dynamic_cast
reinterpret_cast
这是最棘手的使用。它用于重新解释位模式,并且是极低的级别。它主要用于将原始数据位流转换为实际数据或将数据存储在对齐指针的低位中。
然后,要更深入地了解,请阅读以下内容:
Quora @Brian Bi 的回答:https://qr.ae/prz8xL - 非常好、深思熟虑、写得很好、答案很透彻。以下是最后的摘要[顺序重新排列为与上面的顺序相同]:
const_cast
仅更改简历资格;所有其他演员都无法抛弃恒常性。static_cast
执行隐式转换、隐式标准转换的反向转换,以及(可能不安全的)基数到派生转换。dynamic_cast
仅对类层次结构进行向上和向下转换,始终检查请求的转换是否有效。reinterpret_cast
在不更改地址的情况下将一个指针转换为另一个指针,或者在指针及其数值(整数)之间进行转换。
[易于阅读;为每个人编写;内容丰富]https://cplusplus.com/doc/tutorial/typecasting/ - 本文还包含每种类型演员的代码示例!
[迂腐,语言律师,很难读,但更透彻]CppReference 维基:
- https://en.cppreference.com/w/cpp/language/const_cast
- https://en.cppreference.com/w/cpp/language/static_cast
- https://en.cppreference.com/w/cpp/language/dynamic_cast
- https://en.cppreference.com/w/cpp/language/reinterpret_cast
- https://en.cppreference.com/w/cpp/language/explicit_cast
- https://en.cppreference.com/w/cpp/language/implicit_cast
1注意:TutorialsPoint 因抄袭和不引用来源而臭名昭著。我认为他们实际上从主要社区 wiki 答案中获取了措辞,而没有引用它。然而,我确实喜欢他们文章的极度简洁和简单,让初学者很容易开始掌握,或者让那些在面试或考试期间或之前需要复习的人快速复习。
评论