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

Posted

技术标签:

【中文标题】什么时候应该使用 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?【英文标题】:When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used? 【发布时间】:2008-12-01 20:11:07 【问题描述】:

什么是正确的用途:

static_cast dynamic_cast const_cast reinterpret_cast C 风格转换(type)value 函数式转换type(value)

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

【问题讨论】:

这里可能是一个很好的参考:How do you explain the differences among static_cast, reinterpret_cast, const_cast, and dynamic_cast to a new C++ programmer?. 有关使用不同类型强制转换的一些有用的具体示例,您可以查看this other topic 中类似问题的第一个答案。 您可以为上述问题找到非常好的答案。但我想在此再强调一点,@e.James “这些新的 c++ 类型转换操作符无能为力,而 c 风格的类型转换却不能。这些或多或少是为了更好的代码可读性而添加的。” @BreakBadSP 新的演员表只是为了提高代码的可读性。它们的存在是为了让做危险的事情变得更加困难,比如抛弃 const 或投射指针而不是它们的值。 static_cast 做危险事情的可能性比 c 风格的演员要少得多! @FourtyTwo 同意 【参考方案1】:

static_cast 是您应该尝试使用的第一个演员表。它执行诸如类型之间的隐式转换(例如intfloat,或指向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_castvolatile 上也有类似的作用,尽管这种情况不太常见。


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

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


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


C-style castfunction-style cast 是分别使用 (type)objecttype(object) 进行的强制转换,并且在功能上是等效的。它们被定义为以下成功的第一个:

const_cast static_cast(尽管忽略了访问限制) static_cast(见上文),然后是const_cast reinterpret_cast reinterpret_cast,然后是const_cast

因此,在某些情况下,它可以用作其他演员表的替代品,但由于能够演变为reinterpret_cast,因此可能非常危险,并且当需要显式转换时,应该首选后者,除非你是确定static_cast 会成功或reinterpret_cast 会失败。即便如此,请考虑更长、更明确的选项。

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

【讨论】:

dynamic_cast 仅适用于多态类型。您只需要在转换为派生类时使用它。除非您特别需要 dynamic_cast 的功能,否则 static_cast 肯定是第一个选项。一般来说,这不是什么神奇的银弹“类型检查演员”。 很好的答案!一个简短的评论:如果您将 Derived*& 转换为 Base*&,则可能需要 static_cast 来转换层次结构,因为双指针/引用不会自动转换层次结构。两分钟前我遇到了这种(坦率地说,不常见的)情况。 ;-) *“没有其他 C++ 演员能够删除 const(甚至 reinterpret_cast)”......真的吗? reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0))) 呢? 我认为上面缺少的一个重要细节是 dynamic_cast 与 static 或 reinterpret_cast 相比具有运行时性能损失。这很重要,例如在实时软件中。 值得一提的是,reinterpret_cast 通常是处理 API 的一组不透明数据类型时的首选武器【参考方案2】:

使用dynamic_cast 在继承层次结构中转换指针/引用。

使用static_cast 进行普通类型转换。

使用reinterpret_cast 对位模式进行低级重新解释。谨慎使用。

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

【讨论】:

小心使用 dynamic_cast。它依赖于 RTTI,这将无法跨共享库边界按预期工作。仅仅因为您独立构建可执行文件和共享库,因此没有标准化的方法可以在不同构建之间同步 RTTI。由于这个原因,在 Qt 库中存在 qobject_cast,它使用 QObject 类型信息来检查类型。【参考方案3】:

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

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

【讨论】:

其他一些答案的理论很好,但仍然令人困惑,在阅读其他答案后看到这些示例确实使它们都有意义。没有示例,我仍然不确定,但是有了它们,我现在可以确定其他答案的含义。 关于reinterpret_cast的最后一次用法:这不是和static_cast&lt;char*&gt;(&amp;val)一样吗? @LorenzoBelli 当然不是。你试过了吗?后者不是有效的 C++ 并且会阻止编译。 static_cast 仅适用于具有定义转换、继承可见关系或与void * 之间的类型。对于其他一切,还有其他演员表。 reinterpret cast 到任何 char * 类型都允许读取任何对象的表示 - 并且是该关键字有用的唯一情况之一,而不是实现/未定义行为的猖獗生成器。但这不被视为“正常”转换,因此(通常)非常保守的static_cast 不允许。 reinterpret_cast 在您使用数据库等系统软件时非常常见。大多数情况下,您编写自己的页面管理器,它不知道存储在页面中的数据类型是什么,只返回一个 void 指针。它由更高的层次来重新解释演员表并将其推断为他们想要的任何东西。 第一个例子是危险的,因为它假定调用者的行为良好(总是传递一个指向实际EventData对象的指针,仅此而已)。不幸的是,我认为没有任何实用的方法可以以任何有意义的方式对 void 指针进行类型检查。理想情况下,参数将是强类型的。只是一些观察;不是对答案的批评。【参考方案4】:

如果你知道一点内部知识可能会有所帮助......

static_cast

C++ 编译器已经知道如何在诸如floatint 之类的定标器类型之间进行转换。为他们使用static_cast。 当您要求编译器将类型A 转换为B 时,static_cast 调用B 的构造函数,将A 作为参数传递。或者,A 可以有一个转换运算符(即A::operator B())。如果B 没有这样的构造函数,或者A 没有转换运算符,则会出现编译时错误。 如果 A 和 B 在继承层次结构(或 void)中,则从 A* 转换为 B* 始终成功,否则会出现编译错误。 问题:如果将基指针转换为派生指针,但如果实际对象不是真正的派生类型,则不会出错。您会得到错误的指针,并且很可能在运行时出现段错误。 A&amp;B&amp; 也是如此。 问题:从 Derived 转换为 Base 或反之亦然创建 副本!对于来自 C#/Java 的人来说,这可能是一个巨大的惊喜,因为结果基本上是从 Derived 创建的一个切掉的对象。

dynamic_cast

dynamic_cast 使用运行时类型信息来确定强制转换是否有效。例如,如果指针实际上不是派生类型,(Base*)(Derived*) 可能会失败。 这意味着,dynamic_cast 与 static_cast 相比非常昂贵! 对于A*B*,如果转换无效,则dynamic_cast 将返回nullptr。 对于A&amp;B&amp;,如果转换无效,则 dynamic_cast 将抛出 bad_cast 异常。 与其他类型转换不同,存在运行时开销。

const_cast

虽然 static_cast 可以对 const 执行非常量,但它不能反过来。 const_cast 可以做到这两种方式。 一个很方便的例子是遍历一些容器,比如set&lt;T&gt;,它只返回它的元素作为常量,以确保你不会改变它的键。但是,如果您的意图是修改对象的非关键成员,那么应该没问题。您可以使用 const_cast 来移除 constness。 另一个例子是当您想要实现T&amp; SomeClass::foo()const T&amp; SomeClass::foo() const 时。为避免代码重复,您可以应用 const_cast 从另一个函数返回一个函数的值。

reinterpret_cast

这基本上是说在此内存位置获取这些字节并将其视为给定对象。 例如,您可以将 4 字节的 float 加载到 4 字节的 int 中,以查看 float 中的位是什么样的。 显然,如果数据类型不正确,您可能会遇到段错误。 此转换没有运行时开销。

【讨论】:

我添加了转换运算符信息,但还有一些其他问题也应该修复,我觉得更新太多不太舒服。项目是: 1. 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 可能是实现逻辑常量的更好选择。 @Adrian 你是正确的。答案是为或多或少的初学者编写的,我不想用mutable 带来的所有其他复杂情况来压倒他们,交叉铸造等。 @Shital Shah “从 Derived 转换为 Base 或反之会创建新副本!对于来自 C#/Java 的人来说,这可能是一个巨大的惊喜,因为结果基本上是从 Derived 创建的切掉的对象。 "您能否展示一个简单的示例代码以使其更易于理解?谢谢。【参考方案5】:

this 会回答你的问题吗?

我从未使用过reinterpret_cast,我想知道遇到需要它的机箱是否不是设计糟糕的味道。在我工作的代码库中,dynamic_cast 被大量使用。与static_cast 的不同之处在于dynamic_cast 执行运行时检查可能(更安全)或可能不是(更多开销)是您想要的(参见msdn)。

【讨论】:

我将 reintrepret_cast 用于一个目的——从一个 double 中取出位(与我的平台上的 long long 大小相同)。 reinterpret_cast 是必需的,例如用于处理 COM 对象。 CoCreateInstance() 具有 void** 类型的输出参数(最后一个参数),您将在其中传递声明为例如的指针“INetFwPolicy2* pNetFwPolicy2”。为此,您需要编写类似 reinterpret_cast(&pNetFwPolicy2) 的内容。 也许有不同的方法,但我使用reinterpret_cast 从数组中提取数据片段。例如,如果我有一个 char* 包含一个充满打包二进制数据的大缓冲区,我需要移动这些数据并获取不同类型的单个原语。像这样的东西:template&lt;class ValType&gt; unsigned int readValFromAddress(char* addr, ValType&amp; val) /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast&lt;ValType*&gt;(addr))); return sizeof(ValType); 我没用过reinterpret_cast,用处不多。 就我个人而言,我只见过 reinterpret_cast 出于一个原因。我已经看到原始对象数据存储到数据库中的“blob”数据类型,然后当从数据库中检索数据时,reinterpret_cast 用于将这些原始数据转换为对象。【参考方案6】:

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

【讨论】:

不会像 &amp;static_cast&lt;void*&gt;(pNetFwPolicy2) 而不是 static_cast&lt;void**&gt;(&amp;pNetFwPolicy2) 那样工作吗?【参考方案7】:

static_cast vs dynamic_cast vs reinterpret_cast 在向下/向上转换的内部视图

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

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

static_cast:在运行时进行一个地址偏移(运行时影响低)并且没有安全检查向下转换是否正确。

dyanamic_cast:在运行时执行与static_cast 相同的地址偏移,但也使用 RTTI 进行昂贵的安全检查以确保向下转换是正确的。

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

因此,如果您的代码无法检查 nullptr 并采取有效的非中止操作,您应该只使用 static_cast 而不是动态转换。

如果中止是您的代码可以执行的唯一操作,您可能只想在调试版本 (-NDEBUG) 中启用dynamic_cast,否则使用static_cast,例如as done here,不要减慢你的快速运行速度。

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 b211;
    D *dp;
    D d1, 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的内存数据结构内部包含与B1B2相同的内存结构,即:

+0 看起来与 B1 完全一样,其中 D 的 B1 vtable 后跟 int_in_b1 +8 看起来与 B2 完全一样,其中 D 的 B2 vtable 后跟 int_in_b2

因此我们得出关键结论:

向上转换或向下转换只需将指针值移动编译时已知的值

这样,当D 被传递给基本类型数组时,类型转换实际上会计算该偏移量并指向看起来与内存中有效的B2 完全相同的东西,除了这个具有@987654357 的vtable @ 而不是B2,因此所有虚拟调用都是透明的。

例如:

b2s[1] = &d;

只需要得到d+8的地址就可以到达对应的类B2数据结构。

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

从我们看到的标准输出输出:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

因此,在那里完成的隐式static_cast 确实正确计算了从0x7fffffffc930 处的完整D 数据结构到B2 的偏移量,例如0x7ffffffc940 处的偏移量。我们还推断,位于 0x7fffffffc930 和 0x7fffffffc940 之间的很可能是 B1 数据和 vtable。

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

static_cast&lt;D*&gt;(b2s[0]) 0x7fffffffc910:编译器刚刚在编译时上升了 0x10 字节以尝试从 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&lt;D*&gt;(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 条目开始,然后遍历该类层次结构,直到找到来自 b2s[0]D 类型转换的 vtable。

这就是为什么动态转换可能很昂贵的原因!这里是an example where a one liner patch converting a dynamic_cast to a static_cast in a complex project reduced runtime by 33%!。

reinterpret_cast&lt;D*&gt;(b2s[1]) 0x7fffffffc940这个只是盲目相信我们:我们说地址b2s[1]有一个D,编译器不做偏移量计算。

但这是错误的,因为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)

相关问题:

When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used? How is dynamic_cast implemented Downcasting using the 'static_cast' in C++

在 Ubuntu 18.04 amd64、GCC 7.4.0 上测试。

【讨论】:

【参考方案8】:

虽然其他答案很好地描述了 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++ 的作者)的书 The C++ Programming Language 4th edition - page 302 的简短引述。

这种 C 风格的转换比命名的转换运算符危险得多 因为这种符号在大型程序中更难发现,而且程序员想要的转换类型并不明确。

【讨论】:

由于引用了 Stroustrup 的名言而投赞成票。这些天很难找到,尤其是我们经常从非常聪明的人而不是他本人那里听到它。【参考方案9】:

为了理解,让我们考虑下面的代码sn-p:

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

【讨论】:

提供的 sn-p 是一个不好的例子。虽然我同意,确实,它编译。 When 列表大致正确,但大多数意见不足以理解所需的粒度。

以上是关于什么时候应该使用 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 static_cast<int>(x) 而不是 (int)x?

使用static_cast的目的是什么 ()? [重复]

static_cast

[C/C++]_[初级]_[static_cast,reinterpret_cast,dynimic_cast的使用场景和区别]

为啥 static_cast 需要指针或引用?

为啥减法与static_cast溢出?