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

时间:2008-12-01 20:11:07

标签: c++ pointers casting c++-faq

适当的用途是什么:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C式演员(type)value
  • 函数式转换type(value)

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

9 个答案:

答案 0 :(得分:2378)

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

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


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


C风格演员阵营功能风格演员分别使用(type)objecttype(object)进行投射,功能相同。它们被定义为以下第一个成功:

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

因此,它可以在某些情况下用作其他演员阵容的替代品,但由于能够转换为reinterpret_cast而非常危险,而后者在需要明确施法时应该是首选,除非您确定static_cast会成功,或reinterpret_cast会失败。即使这样,也要考虑更长,更明确的选择。

C风格的强制转换在执行static_cast时也会忽略访问控制,这意味着它们可以执行其他强制转换无法执行的操作。但这主要是一个kludge,在我看来,这只是避免C风格演员阵容的另一个原因。

答案 1 :(得分:306)

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

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

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

使用const_cast转换const/volatile。除非你使用const不正确的API,否则请避免这种情况。

答案 2 :(得分:179)

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

以下是 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);
}

答案 3 :(得分:73)

如果你对内部有点了解,这可能会有所帮助......

<强>的static_cast

  • C ++编译器已经知道如何在诸如float之类的scaler类型之间进行转换。使用static_cast
  • 当您要求编译器从类型A转换为B时,static_cast调用B的构造函数将A作为参数传递。或者,A可以有转换运算符(即A::operator B())。如果B没有这样的构造函数,或者A没有转换运算符,则会出现编译时错误。
  • 如果A和B属于继承层次结构(或无效),则从A*转换为B*总是成功,否则会出现编译错误。
  • Gotcha :如果您将基指针强制转换为派生指针,但如果实际对象不是真正的派生类型,那么获取错误。你得到错误的指针,很可能是运行时的段错误。 A&B&相同。
  • Gotcha :从Derived转换为Base或反之创建 new 副本!对于来自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,但它不能反过来。 const_cast可以做到两种方式。
  • 这个方便的一个例子就是遍历像set<T>之类的容器,它只返回它的元素作为const,以确保你不改变它的键。但是,如果您的意图是修改对象的非关键成员,那么它应该没问题。您可以使用const_cast删除constness。
  • 另一个例子是您希望实施T& foo()以及const T& foo()。为避免代码重复,您可以应用const_cast从一个函数返回另一个函数的值。

<强>的reinterpret_cast

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

答案 4 :(得分:12)

this会回答您的问题吗?

我从未使用reinterpret_cast,并想知道是否遇到需要它的情况并不是一种糟糕的设计气味。在dynamic_cast上工作的代码库中使用了很多。与static_cast的区别在于dynamic_cast执行运行时检查可能(更安全)或不可能(更多开销)是您想要的(请参阅msdn)。

答案 5 :(得分:12)

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

#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);

答案 6 :(得分:6)

虽然其他答案很好地描述了C ++强制转换之间的所有差异,但我想简短地说明为什么不应该使用C样式强制转换form(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变量可以进行修改并重新解释

这里是样本。

Type(var)

将C ++强制转换添加到语言中的主要原因是允许开发人员阐明其意图-为什么要执行该强制转换。通过使用在C ++中完全有效的C样式强制转换,您使代码的可读性降低,并且更容易出错,尤其是对于其他未创建代码的开发人员而言。因此,为使代码更具可读性和明确性,您应该始终偏爱C ++强制转换而不是C样式强制转换。

以下是Bjarne Stroustrup(C ++的作者)所著的《 C ++编程语言》第4版-第302页的一句话。

  

这种C型转换比命名转换运算符危险得多   因为这种符号很难在大型程序中发现,并且程序员打算进行的转换类型也不明确。

答案 7 :(得分:6)

static_cast vs dynamic_cast vs reinterpret_cast的内部/内部视图

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

我相信,这将很好地说明这些演员的不同之处:

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

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

    通过此安全检查,您可以通过检查返回nullptr的返回值(表示无效向下转换)来查询运行时基类指针是否为给定类型。

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

    如果中止是代码可以执行的唯一操作,则也许您只想在调试版本(dynamic_cast)中启用-NDEBUG,否则使用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 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所述,为了有效支持虚拟方法调用,D的内存数据结构必须类似于:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

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的内存结构兼容的内存结构。

因此,我们得出了关键结论:

上流或下流只需要将指针值移动编译时已知的值

这样,当D传递给基本类型数组时,类型转换实际上会计算该偏移量,并指向内存中看起来像有效B2的东西:

b2s[1] = &d;

除了该对象具有用于D而不是B2的vtable外,因此所有虚拟调用都透明地工作。

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

从stdout输出中,我们看到:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

因此,在此执行的隐式static_cast可以正确计算从0x7fffffffc930的完整D数据结构到B2的偏移量,就像在0x7fffffffc940处的偏移量一样。我们还可以推断出介于0x7fffffffc930和0x7fffffffc940之间的可能是B1数据和vtable。

然后,在垂头丧气的部分,现在很容易理解无效的失败原因以及原因:

  • static_cast<D*>(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)
    

    所以我们看到海湾合作委员会这样做了:

    • 检查指针是否为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检查,如果输入为NULL,则返回NULL。

    否则,它将在RDX,RSI和RDI中设置一些参数并调用__dynamic_cast

    我现在没有耐心对此做进一步的分析,但是正如其他人所说的那样,唯一可行的方法是__dynamic_cast访问一些表示类层次结构的额外RTTI内存中数据结构

    因此,它必须从该表的B2条目开始,然后遍历该类层次结构,直到从D找到用于b2s[0]类型转换的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<D*>(b2s[1]) 0x7fffffffc940这只是盲目地相信我们:我们说地址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)
    

相关问题:

在Ubuntu 18.04 amd64,GCC 7.4.0上进行了测试。

答案 8 :(得分:0)

要了解,请考虑以下代码段:

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 ++ cast

  • 使用 static_cast 等同于进行值转换的C样式强制转换,或者当我们需要将指针从类显式上载到其超类时。
  • 使用 const_cast 删除const限定词。
  • 使用 reinterpret_cast 将指针类型与整数和其他指针类型进行不安全的转换。仅在我们知道自己在做什么并且了解别名问题时才使用此功能。