【问题标题】:When is a `static_cast<Base*>(static_cast<void*>(derived))` from a pointer to a derived class valid?指向派生类的指针中的 `static_cast<Base*>(static_cast<void*>(derived))` 何时有效?
【发布时间】:2019-08-24 10:08:14
【问题描述】:

对于这个问题,不应该涉及多态性,即没有虚拟方法,没有虚拟基类。以防万一,我的案子不涉及任何这些。

假设我有一个类Derived,它有一个明确的可访问父类Base,没有多态性(没有虚拟方法,没有虚拟基类),但可能涉及间接和/或多重继承。 进一步假设我有一个有效的指针Derived *derived (指向Derived 类型的对象或其子类)。

在这种情况下,我相信static_cast&lt;Base*&gt;(derived) 是有效的 (产生一个有效的可用指针)。当BaseDerived 之间的祖先链涉及多重继承时,此static_cast 可能意味着调整指针以在Base 实例中定位Derived 实例。为此,编译器需要知道继承链,在这种情况下他就是这样做的。但是,如果插入到void * 的中间转换,则该继承链信息对编译器隐藏。尽管如此,这样的静态转换对于哪个继承链有效?我期待以下情况之一:

  • 根本没有?从 void 指针访问 static_cast 是未定义的行为,除非指针确实指向确切的类型。
  • 对于所有没有多重继承的链?然后,编译器可以保证 Base 始终位于 Derived 的开头 - 但是标准是什么意思?
  • 对于在所有中间多重继承链的第一个父类中找到Base 的所有链?也许BaseDerived 的开头仍然匹配?
  • 总是? static_cast 到 void 指针总是可以调整到第一个父级的开头,而 static_cast 从 void 指针撤消该调整。但是对于多重继承,“第一个父级”不一定是所有父级的父级。

【问题讨论】:

  • 解决这个问题的常用方法是 CRTP(又名静态多态性)。那么使用static_cast 是安全的。
  • static_cast&lt;Base*&gt;(derived) 即使存在虚方法也是有效的。但是static_cast&lt;Base*&gt;((void*)derived) 已经错了,只有当基地址与派生地址相同时才给出正式正确的指针。 (假设即使 Derived 仅从 Base 继承,但 Base 没有虚函数,而 Derived 有它 - 地址会有所不同)。无论如何-尝试使用不正确的static_cast&lt;Base*&gt;((void*)derived)而不是正确的static_cast&lt;Base*&gt;(derived)
  • @RbMm:当中间 void* 存储在我无法控制的某个地方时,可能需要进行此类转换。在这种情况下,它是std::experimental::coroutine_handle&lt;&gt;。我想知道是否可以在子类中实现分配/解除分配,将其传递给编译器生成的协程机制,然后只与基类交互。出于效率原因,我宁愿避免运行时多态。
  • intermediate void* - 什么对象?如果您在编译时知道这一点 - 您需要先将 void* 转换为这种类型
  • 这是 C++2a 协程中使用的 promise。在clang + libc++中,指针的实际路径大致是coroutine_handle = __builtin_coro_promise(std::addressof(promise),...,true),然后是promise = *static_cast&lt;Promise*&gt;(__builtin_coro_promise(coroutine_handle,...,false))。这两个调用本质上是在协程转换期间由编译器生成的。

标签: c++ inheritance static-cast upcasting


【解决方案1】:

static_cast&lt;Base*&gt;(static_cast&lt;void*&gt;(derived)) 在 C++ 标准中有一个名称。它被称为reinterpret_cast。在[expr.reinterpret.cast] paragraph 7中指定:

对象指针可以显式转换为不同类型的对象指针。当对象指针类型的纯右值 v 转换为对象指针类型“指向 cv T 的指针”时,结果为static_­cast&lt;cv T*&gt;(static_­cast&lt;cv void*&gt;(v))。 [ 注意:将“指向 T1 的指针”类型的纯右值转换为“指向 T2 的指针”类型(其中 T1 和 T2 是对象类型,并且 T2 的对齐要求不比 T1 严格)并返回其原始类型产生原始指针值。 ——尾注]

reinterpret_cast 是我们告诉编译器将指针视为其他东西。编译器不能或将在此指令下进行任何调整。如果我们撒谎,那么行为就是未定义的。标准是否说明这样的reinterpret_cast 何时有效?它确实如此。在[basic.compound] paragraph 4 定义了指针互转换的概念:

如果满足以下条件,两个对象 a 和 b 是指针可互转换的:

  • 它们是同一个对象,或者
  • 一个是联合对象,另一个是该对象的非静态数据成员 ([class.union]),或者
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有 非静态数据成员,该对象的任何基类子对象 ([class.mem]),或
  • 存在一个对象 c,使得 a 和 c 可以指针互转换,而 c 和 b 可以指针互转换。

如果两个对象是指针可互转换的,那么它们具有相同的 地址,并且可以从指针中获得指向一个的指针 通过reinterpret_­cast 发送给另一个。 [注:数组对象及其 第一个元素不是指针可相互转换的,即使它们有 同一个地址。 ——尾注]

第三个子弹是你的答案。类层次结构中的对象必须遵守限制(从顶层到最派生的standard layout),只有这样才能保证转换提供明确定义的结果。

【讨论】:

  • 除非你认真对待性病。它明确表示 ptrs 是微不足道的类型,所以所有指针可相互转换的东西都很容易理解!
  • @StoryTeller:写得很好,谢谢!现在我正在尝试确定我的课程是否是标准布局......
  • @curiousguy:我不完全理解你的评论。为什么 ptrs 是平凡的类型很重要?这不只是暗示您可以安全地 cast 指针,仅此而已?另一方面,我想知道我是否可以安全地使用那些强制转换的指针。
  • 请参阅我关于戳 ptr Is memcpy of a pointer the same as assignment? Are pointer variables just integers with some operators or are they “symbolic”? Dereferencing a 50% out of bound pointer (array of array) 的位的许多 Q。是的,最后一个代码中的代码似乎很疯狂,因为有一点:如果 ptr 是微不足道的,那么所有这些疯狂的东西都是合法的。一个类型是微不足道的 => 两个相同的位模式在语义上无法区分。
  • 嗯,重点是相同的位模式。所有这些相关的问题都围绕着指向同一类型的指针。但是对于指向不同类型的指针,static_cast一般和memcpy不一样!这是因为向上转换和向下转换需要调整指针的值(即位)。这就是我的问题所在:标准是否声明规则,当编译器必须以不进行调整的方式布局类型时。讲故事的人回答得非常准确;有规则,但比我预期的要窄。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-30
  • 1970-01-01
  • 2018-12-17
  • 2020-01-26
  • 2016-10-27
  • 1970-01-01
  • 2015-05-09
相关资源
最近更新 更多