什么时候应该使用 static_cast、dynamic_cast、const_cast和reinterpret_cast?

When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used?

提问人:e.James 提问时间:12/2/2008 最后编辑:Mateen Ulhaqe.James 更新时间:8/26/2023 访问量:752757

问:

什么是正确的用途:

如何决定在哪些特定情况下使用哪个?

C 指针 强制转换 C++-FAQ

评论

46赞 Nan Xiao 1/8/2016
也许是一个很好的参考:你如何向新的C++程序员解释static_cast、reinterpret_cast、const_cast和dynamic_cast之间的区别?
4赞 TeaMonkie 2/24/2017
有关使用不同类型强制转换的一些有用的具体示例,您可以查看其他主题中类似问题的第一个答案。
5赞 BreakBadSP 10/9/2018
您可以为上面的问题找到非常好的答案。但我想在这里再说一点,@e.James“这些新的 c++ 强制转换算子无能为力,而 c 风格强制转换不行。这些或多或少是为了提高代码的可读性而添加的。
4赞 FourtyTwo 1/13/2020
@BreakBadSP 新的强制转换不仅是为了提高代码的可读性。他们在那里是为了让做危险的事情变得更难,比如抛弃常量或投射指针而不是他们的价值观。static_cast做一些危险的事情的可能性比 C 型演员要少得多!

答:

18赞 andreas buykx 12/2/2008 #1

回答了你的问题吗?

我从来没有用过,想知道遇到需要它的箱子是否是一种糟糕的设计。在我工作的代码库中被大量使用。与此不同的是,执行运行时检查,这可能是您想要的(更安全)也可能不是(更多开销)(请参阅 msdn)。reinterpret_castdynamic_caststatic_castdynamic_cast

评论

4赞 Joshua 11/15/2009
我使用reintrepret_cast用于一个目的 - 从双精度(与我的平台上的长长相同)中取出位。
2赞 Serge Rogatch 5/31/2015
需要reinterpret_cast,例如,用于处理 COM 对象。CoCreateInstance() 具有 void** 类型的输出参数(最后一个参数),您将在其中传递声明为例如“INetFwPolicy2* pNetFwPolicy2”的指针。为此,您需要编写类似 reinterpret_cast<void**>(&pNetFwPolicy2) 的内容。
1赞 James Matta 8/24/2018
也许有一种不同的方法,但我用来从数组中提取数据片段。例如,如果我有一个包含一个包含打包二进制数据的大缓冲区,我需要移动并获取不同类型的单个基元。像这样的东西:reinterpret_castchar*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); }
0赞 Pika Supports Ukraine 10/14/2018
我从来没有用过,它的用途不多。reinterpret_cast
1赞 daparic 8/14/2020
没有遇到评论说明了很多。如果您使用的是其他库,那么您会看到很多 .reinterpret_castC++Creinterpret_cast
427赞 Fred Larson 12/2/2008 #2
  • 用于转换继承层次结构中的指针/引用。dynamic_cast

  • 用于普通类型转换。static_cast

  • 用于位模式的低级重新解释。使用时要格外小心。reinterpret_cast

  • 用于抛弃。避免这种情况,除非您在使用 const 不正确的 API 时遇到困难。const_castconst/volatile

评论

12赞 user3150128 10/23/2018
小心dynamic_cast。它依赖于 RTTI,这不会在共享库边界上按预期工作。仅仅因为您独立构建可执行文件和共享库,就没有标准化的方法来在不同的构建之间同步 RTTI。出于这个原因,在 Qt 库中存在qobject_cast<>它使用 QObject 类型信息来检查类型。
1赞 JMC 3/16/2023
不能用于在不使用虚拟多态性的继承层次结构中转换指针/引用。对于这些层次结构,您必须使用 .dynamic_caststatic_cast
3036赞 25 revs, 19 users 62%coppro #3

static_cast

static_cast是您应该尝试使用的第一个强制转换。它执行诸如类型之间的隐式转换(例如 to 或指针 to )之类的操作,还可以调用显式转换函数(或隐式转换函数)。在许多情况下,明确声明不是必需的,但重要的是要注意语法等同于并且应该避免(稍后会详细介绍)。但是,A 是安全的,并且保证可以调用构造函数。intfloatvoid*static_castT(something)(T)somethingT(something, something_else)

static_cast还可以通过继承层次结构进行强制转换。在向上强制转换(朝向基类)时没有必要,但是在向下强制转换时,只要不通过继承强制转换,就可以使用它。但是,它不执行检查,并且将层次结构向下到实际上不是对象类型的类型是未定义的行为。virtualstatic_cast

const_cast

const_cast可用于删除或添加到变量中;没有其他 C++ 强制转换能够删除它(甚至不能)。需要注意的是,只有当原始变量为 ;如果你用它来删除对未声明的东西的引用,它是安全的。例如,当重载基于 的成员函数时,这可能很有用。它还可用于添加到对象,例如调用成员函数重载。constreinterpret_castconstconstconstconstconstconst

const_cast也同样适用于 ,尽管这不太常见。volatile

dynamic_cast

dynamic_cast专门用于处理多态性。可以将指向任何多态类型的指针或引用强制转换为任何其他类类型(多态类型至少具有一个已声明或继承的虚函数)。您不仅可以将其用于向下投掷,还可以横向投掷,甚至可以向上投掷另一条链。将查找所需的对象,并在可能的情况下将其返回。如果不能,它将在指针的情况下返回,或者在引用的情况下抛出。dynamic_castnullptrstd::bad_cast

dynamic_cast不过,有一些局限性。如果继承层次结构中有多个相同类型的对象(所谓的“可怕的钻石”),并且您没有使用继承,则它不起作用。它也只能通过公共继承——它总是无法通过或继承。然而,这很少成为问题,因为这种形式的继承很少见。virtualprotectedprivate

reinterpret_cast

reinterpret_cast是最危险的演员,应该非常谨慎地使用。它直接将一种类型转换为另一种类型,例如将值从一个指针转换为另一个指针,或将指针存储在 或各种其他令人讨厌的东西中。很大程度上,您得到的唯一保证是,通常,如果将结果强制转换为原始类型,您将获得完全相同的值(但如果中间类型小于原始类型,则不会)。reinterpret_cast也无法进行许多转换。它主要用于特别奇怪的转换和位操作,例如将原始数据流转换为实际数据,或将数据存储在指向对齐数据的指针的低位中。intreinterpret_cast

C 型铸造和功能型铸造

C 样式转换和函数样式转换分别是使用 或 的转换,并且在功能上是等效的。它们被定义为以下第一个成功:(type)objecttype(object)

  • const_cast
  • static_cast(虽然忽略了访问限制)
  • static_cast(见上文),然后const_cast
  • reinterpret_cast
  • reinterpret_cast然后const_cast

因此,在某些情况下,它可以用作其他强制转换的替代品,但由于能够下放为 ,因此可能非常危险,当需要显式强制转换时,应首选后者,除非您确定会成功或会失败。即便如此,也要考虑更长、更明确的选项。reinterpret_caststatic_castreinterpret_cast

C 样式强制转换在执行 时也会忽略访问控制,这意味着它们能够执行其他强制转换无法执行的操作。不过,这主要是一个问题,在我看来,这只是避免 C 型演员的另一个原因。static_cast

评论

23赞 jalf 12/2/2008
dynamic_cast仅适用于多态类型。只有在强制转换为派生类时才需要使用它。 static_cast当然是第一个选择,除非你特别需要dynamic_cast的功能。一般来说,这不是什么神奇的银弹“类型检查演员”。
3赞 bartgol 4/8/2013
很好的答案!一个简短的评论:static_cast可能需要强制转换层次结构,以防您将 Derived*& 强制转换为 Base*&,因为双指针/引用不会自动强制转换层次结构。两分钟前,我遇到了这种情况(坦率地说,并不常见)。;-)
9赞 user541686 1/15/2015
*“没有其他 C++ 强制转换能够删除(甚至)”...真?怎么样?constreinterpret_castreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
57赞 jfritz42 7/30/2015
我认为上面缺少的一个重要细节是,与静态或reinterpret_cast相比,dynamic_cast具有运行时性能损失。这一点很重要,例如在实时软件中。
11赞 Class Skeleton 8/4/2015
值得一提的是,在处理 API 的一组不透明数据类型时,这通常是首选的武器reinterpret_cast
238赞 Sumit Arora 1/21/2014 #4

(上面已经给出了很多理论和概念上的解释)

以下是我使用 static_castdynamic_castconst_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);
}

评论

42赞 Solx 4/29/2014
其他一些答案的理论很好,但仍然令人困惑,在阅读其他答案后看到这些例子确实使它们都有意义。也就是说,如果没有这些例子,我仍然不确定,但有了它们,我现在确定其他答案的含义。
1赞 Lorenzo Belli 5/27/2016
关于reinterpret_cast的最后一次使用:这和使用不一样吗?static_cast<char*>(&val)
5赞 underscore_d 7/17/2016
@LorenzoBelli当然不是。你试过了吗?后者无效的 C++ 并阻止编译。 仅适用于具有已定义转换、通过继承的可见关系或 to/from 的类型。对于其他一切,还有其他演员。 允许读取任何对象的表示形式 - 并且是该关键字有用的唯一情况之一,而不是猖獗的实现/未定义行为的生成器。但这不被认为是“正常”的转换,因此(通常)非常保守的人不允许这样做。static_castvoid *reinterpret castchar *static_cast
2赞 Sohaib 5/17/2017
当您使用数据库等系统软件时,reinterpret_cast很常见。大多数情况下,您编写自己的页面管理器,它不知道页面中存储的数据类型是什么,只是返回一个无效指针。由更高的级别来重新解释演员表并将其推断为他们想要的任何东西。
1赞 Brian A. Henning 1/29/2019
第一个示例很危险,因为它假定调用方具有良好的行为(始终将指针传递到实际对象,而不是其他任何指针)。不幸的是,我认为没有任何实用的方法可以以任何有意义的方式对空白指针进行类型检查。理想情况下,参数将是强类型的。只是一些观察;不是对答案的批评。EventData
17赞 Serge Rogatch 5/31/2015 #5

除了到目前为止的其他答案之外,这里有一个不明显的例子,其中是不够的,所以需要。假设有一个函数,该函数在输出参数中返回指向不同类(不共享公共基类)的对象的指针。这种函数的一个真实例子是 CoCreateInstance() (参见最后一个参数,实际上是 )。假设您从此函数请求特定类的对象,因此您事先知道指针的类型(您经常对 COM 对象执行此操作)。在这种情况下,您不能将指向指针的指针转换为:您需要。static_castreinterpret_castvoid**void**static_castreinterpret_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_castreinterpret_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);

评论

1赞 jp48 8/15/2019
它不会像 而不是 ?&static_cast<void*>(pNetFwPolicy2)static_cast<void**>(&pNetFwPolicy2)
146赞 Shital Shah 12/11/2016 #6

如果您了解一点内部情况,可能会有所帮助......

static_cast

  • C++编译器已经知道如何在缩放器类型(如to )之间进行转换。为他们使用。floatintstatic_cast
  • 当您要求编译器将类型转换为 时,调用作为参数传递的构造函数。或者,可以有一个转换运算符(即 )。如果没有这样的构造函数,或者没有转换运算符,则会出现编译时错误。ABstatic_castBAAA::operator B()BA
  • 如果 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 中的位的外观。floatintfloat
  • 显然,如果数据与类型不符,则可能会出现段错误。
  • 此强制转换没有运行时开销。

评论

0赞 Adrian 11/13/2018
我添加了转换运算符信息,但还有其他一些事情应该修复,我觉得更新太多并不舒服。项目是: 1.你得到UB,如果你幸运的话,这可能会导致运行时出现段错误。2.动态铸件也可用于交叉铸造。3. 在某些情况下,常量石膏型会导致 UB。使用可能是实现逻辑恒定性的更好选择。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
1赞 Shital Shah 11/16/2018
@Adrian你在所有方面都是正确的。答案是为或多或少处于初学者水平的人编写的,我不想用所有其他复杂性来压倒他们,交叉投射等。mutable
0赞 John 10/27/2021
@Shital Shah “从衍生到基础或反之亦然创建新副本!对于来自C#/Java的人来说,这可能是一个巨大的惊喜,因为结果基本上是一个从Derived创建的被砍掉的对象。您能否展示一个简单的示例代码以使其更易于理解?谢谢。
2赞 frakod 7/19/2022
std::bit_cast 呢?
13赞 Timmy_A 8/22/2018 #7

虽然其他答案很好地描述了 C++ 强制转换之间的所有差异,但我想添加一个简短的注释,为什么您不应该使用 C 样式强制转换和 .(Type) varType(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 样式的转换转换操作器比指定的转换运算符危险得多 因为在大型程序中很难发现符号,并且程序员想要的转换类型并不明确。

评论

1赞 daparic 8/14/2020
由于引用了 Stroustrup 的报价而投票。这些天很难找到,尤其是我们经常从非常聪明的人那里听到它,而不是这个人自己。
0赞 Gabriel Staples 3/24/2023
投票,因为它是信息性的。但是,我仍然更喜欢我的 C++ 代码中的 C 样式强制转换。我现在正在通过阅读这里的答案并查看这里的 CplusPlus.com 来对它进行更多研究:cplusplus.com/doc/tutorial/typecasting。目前尚未确定我是否会转换并开始在 C++ 中使用 C++ 样式的强制转换。我相当讨厌 C++ 的冗长和类型安全性过度可读性方面。
5赞 pkthapa 12/21/2018 #8

为了理解,让我们考虑以下代码片段:

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 可以不安全地将指针类型与整数和其他指针类型进行转换。只有当我们知道自己在做什么并且了解混叠问题时,才使用它。

评论

0赞 daparic 8/14/2020
提供的代码片段是一个糟糕的示例。虽然我同意这一点,但它确实是编译的。“当”列表模糊正确,但大多充满了不足以理解所需粒度的意见。
20赞 Ciro Santilli OurBigBook.com 2/26/2020 #9

static_cast vs dynamic_cast vs reinterpret_cast downcast/upcast 上的内部视图

在这个答案中,我想在一个具体的上行/下行示例中比较这三种机制,并分析底层指针/内存/程序集会发生什么,以具体理解它们的比较方式。

我相信这将很好地直观地了解这些演员的不同之处:

  • static_cast:在运行时执行一个地址偏移(运行时影响小),并且不安全检查下行是否正确。

  • dyanamic_cast:在运行时执行相同的地址偏移,例如,并且还使用RTTI进行昂贵的安全检查,以确保下行是否正确。static_cast

    此安全检查允许您在运行时通过检查指示无效下转换的返回值来查询基类指针是否属于给定类型。nullptr

    因此,如果您的代码无法检查这一点并执行有效的非中止操作,则应仅使用而不是动态强制转换。nullptrstatic_cast

    如果中止是你的代码可以采取的唯一操作,也许你只想启用调试版本(),并使用其他方法,例如,如此处所做的那样,以免减慢你的快速运行速度。dynamic_cast-NDEBUGstatic_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

关键事实是,其中的内存数据结构包含与 和 相同的内存结构,即:DB1B2

  • +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 而不是 ,因此所有虚拟调用都是透明的。DB2DB2

例如:

b2s[1] = &d;

只需要获取地址 + 8 即可达到相应的类 B2 数据结构。d

现在,我们终于可以回到类型转换和对具体示例的分析。

从 stdout 输出中,我们看到:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

因此,在那里完成的隐式确实正确地计算了从0x7fffffffc930处的完整数据结构到0x7fffffffc940处的类似数据结构的偏移量。我们还推断,介于 0x7fffffffc930 和 0x7fffffffc940 之间的可能是数据和 vtable。static_castDB2B1

然后,在向下的部分,现在很容易理解无效部分是如何失败的以及为什么:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910:编译器只是在编译时向上0x10字节,以尝试从 A 转到包含B2D

    但是因为不是 ,它现在指向一个未定义的内存区域。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。B2Db2s[0]

    这就是为什么动态投射可能很昂贵的原因!下面是一个示例,在复杂项目中,将dynamic_cast转换为static_cast的单衬里补丁将运行时间缩短了 33%!

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940这个只是盲目地相信我们:我们说有一个 at 地址,编译器不进行偏移计算。Db2s[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 上测试。

1赞 Adrian 4/17/2022 #10

在其他答案中没有提到的一个很好的功能是,它允许我们为函数类型创建一种指针。通常,对于对象类型,用于检索存储在 :reinterpret_castvoid*static_castvoid*

  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_castptrprintreinterpret_cast

2赞 Özgür Murat Sağdıçoğlu 8/28/2022 #11

让我们在示例中看看 和 的区别:reinterpret_caststatic_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

请注意,输出 和 以及 和 是不同的。为什么?在这两种情况下,其中一个是相同类型的输入,另一个是相同类型的相同输入。1234static_castreinterpret_cast

情况如下图所示:

Visualization

C包含 ,但 的起始地址与 不同。 正确计算 within 的地址。但是,返回我们作为输入给出的相同地址,这对于这种情况是不正确的:该地址没有。BBCstatic_castBCreinterpret_castB

但是,在 和 指针之间转换时,两个强制转换都返回相同的结果,因为它们恰好从同一位置开始,顺便说一句,标准无论如何都不能保证这一点。AC

2赞 Gabriel Staples 3/25/2023 #12

我认为我们需要一个更适合初学者的解释,在我自己研究了这个主题之后,我认为我发现的最好的是这里:https://www.tutorialspoint.com/When-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used-in-Cplusplus [1]

什么时候应该在 C++ 中使用 、 和?static_castdynamic_castconst_castreinterpret_cast

const_cast
可用于删除或向变量添加 const。如果需要在变量中添加/删除恒定性,这可能很有用。

static_cast
这用于普通/普通类型转换。这也是负责隐式类型 co[nv]ersion 的强制转换,也可以显式调用。您应该在将 float 转换为 int、char 到 int 等情况下使用它。

dynamic_cast
此强制转换用于处理多态性。只有在强制转换为派生类时才需要使用它。这专门用于从基类转换为派生类的继承。

  • 我自己补充的话:它允许安全地向下转换,从 ptr 转换为基类(通过获取从该基类继承的派生类的地址创建)到 ptr 到派生类,确保 ptr 实际上指向一个完整的、完整的派生类对象(如果不是,则返回一个)。这里的“”部分有一个很好的代码示例:https://cplusplus.com/doc/tutorial/typecasting/。(我在这里的回答中讨论了该代码)。nullptrdynamic_cast

reinterpret_cast
这是最棘手的使用。它用于重新解释位模式,并且是极低的级别。它主要用于将原始数据位流转换为实际数据或将数据存储在对齐指针的低位中。

然后,要更深入地了解,请阅读以下内容:

  1. Quora @Brian Bi 的回答:https://qr.ae/prz8xL - 非常好、深思熟虑、写得很好、答案很透彻。以下是最后的摘要[顺序重新排列为与上面的顺序相同]:

    • const_cast仅更改简历资格;所有其他演员都无法抛弃恒常性。
    • static_cast执行隐式转换、隐式标准转换的反向转换,以及(可能不安全的)基数到派生转换。
    • dynamic_cast仅对类层次结构进行向上和向下转换,始终检查请求的转换是否有效。
    • reinterpret_cast在不更改地址的情况下将一个指针转换为另一个指针,或者在指针及其数值(整数)之间进行转换。
  2. 主要的社区 wiki 答案在这里

  3. [易于阅读;为每个人编写;内容丰富]https://cplusplus.com/doc/tutorial/typecasting/ - 本文还包含每种类型演员的代码示例!

  4. [迂腐,语言律师,很难读,但更透彻]CppReference 维基:

    1. https://en.cppreference.com/w/cpp/language/const_cast
    2. https://en.cppreference.com/w/cpp/language/static_cast
    3. https://en.cppreference.com/w/cpp/language/dynamic_cast
    4. https://en.cppreference.com/w/cpp/language/reinterpret_cast

    5. https://en.cppreference.com/w/cpp/language/explicit_cast
    6. https://en.cppreference.com/w/cpp/language/implicit_cast

1注意:TutorialsPoint 因抄袭和不引用来源而臭名昭著。我认为他们实际上从主要社区 wiki 答案中获取了措辞,而没有引用它。然而,我确实喜欢他们文章的极度简洁和简单,让初学者很容易开始掌握,或者让那些在面试或考试期间或之前需要复习的人快速复习