关于C++:什么时候应该使用static_cast, dynamic_cast, const_cast and reinterpret_cast

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

以下各项的正确用途是什么:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C型铸造(type)value
  • 功能型铸造type(value)

在特定的情况下,如何决定使用哪种方法?


static_cast是您应该尝试使用的第一个演员表。它可以执行类型之间的隐式转换(例如intfloat或指向void*的指针),还可以调用显式转换函数(或隐式转换函数)。在许多情况下,显式地声明static_cast是不必要的,但需要注意的是,T(something)语法等同于(T)something并且应该避免(稍后将详细介绍)。但是,T(something, something_else)是安全的,并且保证调用构造函数。

static_cast也可以通过继承层次进行强制转换。向上(向基类)强制转换是不必要的,但向下强制转换时,只要不通过virtual继承强制转换,就可以使用它。但是,它不进行检查,并且对于static_cast来说,它是未定义的行为,从层次结构向下到实际上不是对象类型的类型。

EDCOX1〔11〕可用于删除或添加EDCOX1·12的变量,不存在其他C++的CAST能够移除它(甚至EDCOX1×13)。需要注意的是,只有当原始变量为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样式转换和函数样式转换分别使用(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时也会忽略访问控制,这意味着它们具有执行其他强制转换无法执行的操作的能力。不过,这主要是一个蹩脚的说法,在我看来,这只是避免使用C型铸造的另一个原因。


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

普通类型转换使用static_cast

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

使用const_cast丢弃const/volatile。除非您一直在使用常量不正确的API,否则请避免这种情况。


(上面有很多理论和概念上的解释)

下面是我使用静态、动态、const、reinterpret时的一些实际示例。

(另请参考此部分了解解释:http://www.cplusplus.com/doc/tutorial/typecasting/)

静态铸造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
  .....
}

动态铸造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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( ... )*/{
        // ...
    }
}

康斯坦斯卡斯特:

1
2
3
4
5
6
7
8
// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

重新解释铸造:

1
2
3
4
5
6
7
typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read.

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}


如果你知道一点内在的东西可能会有所帮助…

静态铸造

  • C++编译器已经知道如何在标量类型(例如浮点到int)之间进行转换。
  • 当要求编译器从类型A转换为B时,static_cast调用B的构造函数,将A作为参数传递。或者,A可以有一个转换运算符(即A::operator B())。如果B没有这样的构造函数,或者A没有转换运算符,则会得到编译时错误。
  • A*B*的强制转换总是成功的,如果a和b处于继承层次(或无效),否则会出现编译错误。
  • gotcha:如果您将基指针强制转换为派生指针,但如果实际对象不是真正的派生类型,则不会得到错误。你得到了坏的指针,很可能在运行时出现一个segfault。同样适用于A&B&
  • gotcha:从派生到基的强制转换或viceversa创建新副本!对于来自C/Y/Java的人来说,这可能是一个巨大的惊喜,因为结果基本上是从派生出来的切掉的对象。

动态铸件

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

康斯特卡斯特

  • 虽然静态类型转换可以对常量执行非常量转换,但它不能向其他方向转换。警察演员可以同时扮演两种角色。
  • 其中一个很方便的例子是迭代一些容器,比如set,它只返回常量元素,以确保您不更改其键。但是,如果您的目的是修改对象的非关键成员,那么它应该是正常的。你可以使用const-cast来移除const。
  • 另一个例子是当您想要实现T& foo()const T& foo()时。为了避免代码重复,可以应用const-cast从另一个函数返回一个函数的值。

重新解释铸模

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


除其他答案外,这里还有一个不明显的例子,即static_cast不足以满足reinterpret_cast的需要。假设有一个函数在输出参数中返回指向不同类(不共享公共基类)的对象的指针。这类函数的一个实际示例是CoCreateInstance()(参见最后一个参数,实际上是void**)。假设您从这个函数中请求特定的对象类,所以您提前知道指针的类型(通常对COM对象这样做)。在这种情况下,不能使用static_cast将指向指针的指针强制转换为void**:您需要reinterpret_cast(&yourPointer)

在代码中:

1
2
3
4
5
6
7
8
#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(以额外变量为代价):

1
2
3
4
5
6
7
8
9
#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);

这能回答你的问题吗?

我从来没有用过reinterpret_cast,我想知道遇到一个需要它的案例是否不是一种糟糕的设计味道。在代码库中,我在dynamic_cast上做了很多工作。与static_cast的区别在于dynamic_cast执行运行时检查,这可能(更安全)也可能(开销更大)是您想要的(参见msdn)。


虽然其他答案很好地描述了C++之间的所有差异,但我想添加一个简短的注释,说明为什么不应该使用C样式的EDCX1 0和EDCX1 1。

对于C++初学者来说,C风格的造型看起来像是C++上的超集操作(StasySkyCase<>)、DyrimixCase<>()、CistasyCase<>()、RealTytCase<>()),有人可以在C++的C++上更喜欢它们。实际上,C-StyleCast是一个超集,写起来更短。

C样式的强制转换的主要问题是隐藏了强制转换的开发者真实意图。C样式的强制转换可以执行几乎所有类型的强制转换,从静态强制转换<>()和动态强制转换<>()到潜在危险的强制转换,如const强制转换<>(),其中const修饰符可以删除,因此const变量可以修改和重新解释,甚至可以将整数值重新解释为指针。

这是样品。

1
2
3
4
5
6
7
8
9
10
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++编程语言第四版-第302页。

This C-style cast is far more dangerous than the named conversion operators
because the notation is harder to spot in a large program and the kind of conversion intended by the programmer is not explicit.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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可用于将指向对象的指针转换为指向任何无关对象类型的指针。

需要注意的一个问题是:动态资源类型转换在运行时会失败,但是在大多数编译器上,它也会失败编译,因为在被转换的指针的结构中没有虚拟函数,这意味着动态资源类型转换只能使用多态类指针。

何时使用C++ CAST:

  • 使用static_cast作为进行值转换的C样式转换的等价物,或者当我们需要显式地向上转换从类到其超类的指针时。
  • 使用const-cast删除const限定符。
  • 使用reinterpret_cast对整数和其他指针类型的指针类型进行不安全的转换。只有当我们知道我们在做什么,并且我们了解别名问题时,才使用这个方法。