繁体   English   中英

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

[英]When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used?

什么是正确的用途:

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

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_cast也同样适用于volatile ,尽管这种情况不太常见。


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 避免这种情况,除非您使用 const 不正确的 API 被卡住。

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

以下是我使用static_castdynamic_castconst_castreinterpret_cast时的一些实际示例

(也参考这个来理解解释: 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);
}

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

static_cast

  • C++ 编译器已经知道如何在诸如floatint之类的缩放器类型之间进行转换。 为他们使用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<T>这样的容器,它只返回它的元素作为 const 以确保你不会改变它的键。 但是,如果您的意图是修改对象的非关键成员,那么应该没问题。 您可以使用 const_cast 删除 constness。
  • 另一个例子是当你想实现T& SomeClass::foo()以及const T& SomeClass::foo() const时。 为避免代码重复,您可以应用 const_cast 从另一个函数返回一个函数的值。

reinterpret_cast

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

能回答你的问题吗?

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

除了到目前为止的其他答案之外,这是一个不明显的例子,其中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 vs dynamic_cast vs reinterpret_cast internals view on a downcast/upcast

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

我相信这将对这些演员的不同之处提供一个很好的直觉:

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

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

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

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

    如果中止是您的代码可以采取的唯一操作,那么您可能只想在调试版本 ( -NDEBUG ) 中启用dynamic_cast ,否则使用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

其中setarch 用于禁用 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-like数据结构。

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

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

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

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

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

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

    所以我们看到 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 检查,如果输入为 NULL,则返回 NULL。

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

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

    因此,它必须从该表的B2条目开始,然后遍历该类层次结构,直到找到来自b2s[0]D类型转换的 vtable。

    这就是为什么动态转换可能很昂贵的原因! 这是一个示例,其中在复杂项目中将dynamic_cast转换为static_cast的单行补丁将运行时间减少了 33%! .

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

相关问题:

在 Ubuntu 18.04 amd64、GCC 7.4.0 上测试。

虽然其他答案很好地描述了 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 风格的转换比命名转换运算符危险得多,因为这种符号在大型程序中更难发现,而且程序员想要的转换类型并不明确。

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

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

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

我们通过一个例子来看看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;
}

产生 output:

0x7ffcede34f0c
0x7ffcede34f08 // 1
0x7ffcede34f0c // 2

0x7ffcede34f0c
0x7ffcede34f10 // 3
0x7ffcede34f0c // 4

0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c

0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c

请注意 output 12以及34不同。 这是为什么? 其中一个是static_cast ,另一个是reinterpret_cast在这两种情况下对相同输入的相同类型。

情况可以用下图形象化:

可视化

C包含一个BB的起始地址与C static_cast正确计算BC内的地址。 然而, reinterpret_cast返回的地址与我们输入的地址相同,这对于这种情况是不正确的:该地址没有B

但是,在AC指针之间转换时,两种转换都返回相同的结果,因为它们碰巧从相同的位置开始,顺便说一句,标准并不能保证。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM