ChatGPT解决这个技术问题 Extra ChatGPT

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

什么是正确的用途:

static_cast

dynamic_cast

const_cast

reinterpret_cast

(type)value (C 风格转换)

type(value) (函数式转换)

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

有关使用不同类型强制转换的一些有用的具体示例,您可以查看 this other topic 中类似问题的第一个答案。
您可以为上述问题找到非常好的答案。但我想在此再强调一点,@e.James “这些新的 c++ 类型转换运算符无能为力,而 c 风格的类型转换则不能。这些或多或少是为了更好的代码可读性而添加的。”
@BreakBadSP 新的演员表不仅是为了更好的代码可读性。它们的存在是为了让做危险的事情变得更加困难,比如抛弃 const 或投射指针而不是它们的值。 static_cast 做一些危险的事情的可能性比 ac 风格的演员要少得多!

2
24 revs, 18 users 67%

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

重新解释_cast,然后是 const_cast

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

C 风格的转换在执行 static_cast 时也会忽略访问控制,这意味着它们能够执行其他转换无法执行的操作。不过,这主要是一个杂物,在我看来,这只是避免 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 相比具有运行时性能损失。这很重要,例如在实时软件中。
值得一提的是,在处理 API 的一组不透明数据类型时,reinterpret_cast 通常是首选武器
M
Mateen Ulhaq

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

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

使用 reinterpret_cast 对位模式进行低级重新解释。使用时要格外小心。

使用 const_cast 来抛弃 const/volatile。避免这种情况,除非您使用 const 不正确的 API 被卡住。


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

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

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

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

如果您了解一点内部知识可能会有所帮助...

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 这样的容器,它只返回它的元素作为 const 以确保你不会改变它的键。但是,如果您的意图是修改对象的非关键成员,那么应该没问题。您可以使用 const_cast 删除 constness。

另一个例子是当你想实现 T& SomeClass::foo() 以及 const T& 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 创建的切掉的对象。”您能否展示一个简单的示例代码以使其更易于理解?谢谢。
那么 std::bit_cast 呢?
t
talnicolas

this 是否回答了您的问题?

我从未使用过 reinterpret_cast,并且想知道遇到需要它的案例是否不是糟糕的设计。在我处理 dynamic_cast 的代码库中被大量使用。与 static_cast 的区别在于,dynamic_cast 执行运行时检查,这可能(更安全)或可能不是(更多开销)是您想要的(请参阅 msdn)。


我将 reintrepret_cast 用于一个目的——从双精度中获取位(与我的平台上的 long long 大小相同)。
例如,处理 COM 对象时需要 reinterpret_cast。 CoCreateInstance() 具有 void** 类型的输出参数(最后一个参数),您将在其中传递声明为例如“INetFwPolicy2* pNetFwPolicy2”的指针。为此,您需要编写类似 reinterpret_cast(&pNetFwPolicy2) 的内容。
也许有不同的方法,但我使用 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 用于将此原始数据转换为对象。
L
Lii

除了到目前为止的其他答案之外,这里还有一个不明显的例子,其中 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) 那样工作吗?
C
Ciro Santilli Путлер Капут 六四事

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

其中 setarchused 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 完全相同的东西,除了这个具有 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(b2s[0]) 0x7fffffffc910: 编译器刚刚在编译时上升 0x10 字节尝试从 B2 转到包含 D 但因为 b2s[0] 不是 D,它现在指向一个未定义的内存区域。反汇编为:49 dp = static_cast(b2s[0]); 0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fcc <+418>: 48 85 c0 测试%rax,%rax 0x0000000000000fcf <+421>: 74 0+xfdb 0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fd5 <+427>: 48 83 e8 10 子 $0x10,%rax 0x00000030000000fdjmp9 <+ ()+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

检查指针是否为NULL,如果是则返回NULL

否则,减去 0x10 得到不存在的 D

dynamic_cast(b2s[0]) 0:C++实际上发现强制转换无效并返回nullptr!这不可能在编译时完成,我们将通过反汇编确认: 59 dp = dynamic_cast(b2s[0]); 0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x00000000000010f0 <+710>: 48 85 c0 测试%rax,%rax 0x00000000000010f3 <+713>11112<+713>: d je 11() 744> 0x0000000000000010F5 <+715>:B9 10 00 00 00 00 00 00 00 00 x10,%ecx 0x00000000000000001010110fa <+720>:48 8d 15 f7 0b 20 00 lea 0x200bf7(%RIP),%RIP),%RDX#0x201CF8 <+700000000000000000000000000000000000000000000000个>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2> 0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi 0x000000000000110b <+ff>7000110b <+7 0xcd0 <__ dynamic_cast@plt> 0x0000000000001110 <+742>:EB 05 JMP 0x1117 0x000000000000001112 <+7444>:B8 00 00 00 00 00 00 00 00 00 00 00 00 00 x 0x0,%eax 0x0,eax0x0000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111往mov %rax,-0x68(%rbp) 首先进行 NULL 检查,如果输入为 NULL,则返回 NULL。否则,它会在 RDX、RSI 和 RDI 中设置一些参数并调用 __dynamic_cast。我现在没有耐心对此进行进一步分析,但正如其他人所说,唯一可行的方法是让 __dynamic_cast 访问一些表示类层次结构的额外 RTTI 内存数据结构。因此,它必须从该表的 B2 条目开始,然后遍历该类层次结构,直到找到来自 b2s[0] 的 D 类型转换的 vtable。这就是为什么动态转换可能很昂贵的原因!这是一个示例,其中在复杂项目中将 dynamic_cast 转换为 static_cast 的单行补丁将运行时间减少了 33%!。

reinterpret_cast(b2s[1]) 0x7fffffffc940 这个只是盲目相信我们:我们说地址b2s[1]有一个D,编译器不做偏移量计算。但这是错误的,因为D实际上在0x7fffffffc930,0x7fffffffc940是D内部的类似B2的结构!所以垃圾被访问了。我们可以从可怕的 -O0 程序集中确认这一点,它只是移动了值: 70 dp = reinterpret_cast(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 上测试。


T
Timmy_A

虽然其他答案很好地描述了 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 的报价而投票。这些天很难找到,尤其是我们经常从非常聪明的人而不是男人本人那里听到它。
p
pkthapa

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

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


提供的片段是一个不好的例子。虽然我同意它确实可以编译。 When 列表模糊地正确,但大多数意见不足以理解所需的粒度。
A
Adrian

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)。