【问题标题】:Is it safe to reinterpret_cast between a Type and a Wrapper<Type>?在 Type 和 Wrapper<Type> 之间重新解释转换是否安全?
【发布时间】:2020-10-27 03:32:29
【问题描述】:

假设我们有这样的类模板Wrapper

template <class T>
struct Wrapper { T wrapped; };

对于reinterpret_castTypeWrapper&lt;Type&gt; 之间的哪些类型是安全的?没有?标准布局?全部?

假设我们创建了其中一个对象(TypeWrapper&lt;Type&gt;),并通过另一个对象来读写这个对象。示例(live on godbolt.org):

void F1() {
    std::stringstream ss;
    ss << "Hello";
    reinterpret_cast<Wrapper<std::stringstream>&>(ss).wrapped << " world";
}

void F2() {
    Wrapper<std::stringstream> ss;
    ss.wrapped << "Hello";
    reinterpret_cast<std::stringstream&>(ss) << " world";
}

阅读this answer这个领域的cmets在标准上似乎不是很明确。我认为所有编译器都会生成按预期工作的代码(即一种类型的值可以转换为另一种类型),但标准目前可能无法保证这一点。如果没有,问题就出现了:标准能否保证这些强制转换的明确行为,或者在这种情况下保证任何东西是不可能/不切实际的?

因为我很确定,这些演员阵容确实有效。

【问题讨论】:

  • "阅读这个答案的 cmets 这个领域在标准中似乎不是很明确。" 阅读这些 cmets 只告诉我有些人不知道认为 C++ 应该 是那样的,而不是 C++ 实际上不是那样的。该评论线程中的许多人都以他们对此事的看法而闻名,这与 C++ 对象模型的实际工作方式不一致。
  • "标准能否保证这些强制转换的明确行为,或者在这种情况下保证任何东西是不可能/不切实际的?" 这个问题没有任何意义。标准“可以”保证任何事情。 C++ 标准通常不关注编译器认为“定义明确的行为”然后说“那是 C++”。标准定义什么是明确定义的,如果在特定情况下某些其他东西碰巧适用于特定实现,那与标准无关。
  • @NicolBolas:但是,如果某些未标准化的东西适用于所有编译器,并且具有实际意义,我认为它可能会被放入标准中。
  • 这通常不是标准中某些内容的原因。尤其是当它涉及诸如 C++ 对象模型之类的基本概念时。它最终必须是有意义的,而你所说的至少有一半没有。
  • "我可以向你保证,这是有道理的" 不,它没有。如果那里没有Wrapper&lt;Type&gt; 对象,那么尝试访问它是没有意义的。你不能假装那里没有一个物体。为了使这有意义,您必须从根本上改变“对象”对 C++ 的意义的idea。您基本上是在说“对象”只是您目前看待一段记忆的方式,除此之外没有任何意义。

标签: c++ language-lawyer undefined-behavior reinterpret-cast strict-aliasing


【解决方案1】:

绝不允许将T(不是Wrapper&lt;T&gt; 的成员)重新解释为Wrapper&lt;T&gt;F1 示例)。

另一方面,我相信标准布局类允许将Wrapper&lt;T&gt; 重新解释为T:(F2 示例)

[basic.compound]/4.3

两个对象 abpointer-interconvertible 如果:

——一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者...

在它的正下方:

如果两个对象是指针可互转换的,那么它们具有相同的地址,并且可以通过reinterpret_­cast 从指向另一个对象的指针中获得指向其中一个对象的指针。 ...

请注意,虽然此规则是对称的,但它要求两个对象都实际存在。如果您对 T 的引用指向 Wrapper&lt;T&gt; 的成员,那么您可以将其重新解释为 Wrapper&lt;T&gt;(反之亦然)。但如果它指向一个不是Wrapper&lt;T&gt; 成员的T 对象,那么它将是UB。


免责声明:“不允许这样那样的重新解释”我的意思是访问 reinterpret_cast 的结果会导致 UB。演员表本身不应导致 UB。

【讨论】:

  • 这是一个永久链接,还是会在几年后悬空?我相信eel.is/c++draft 始终是当前的草案标准,所以它可能会在一段时间内悬而未决。除此之外,假设我们有struct A : B1, B2, B3 {};。我们显然不能在AB2 之间使用reinterpret_cast。因此,如果两个对象是pointer-interconvertible,这并不意味着我们可以像我在问题中那样使用它们。
  • @Dr.Gut 该链接不是永久性的,但部分名称(如[foo.bar])应该是稳定的,因此您可以在任何版本的标准中查找它。
  • 在该部分的正下方 “如果两个对象是指针可互转换的,那么它们具有相同的地址,并且可以通过指向另一个对象的指针获得指向另一个对象的指针reinterpret_cast。”。我不知道为什么它没有说只有第一个碱基必须与对象具有相同的地址......
  • 请注意 Denying the antecedent 不应应用于第二个引号(例如,通过字符类型为标准布局对象起别名是有效的,即使 charT 不可相互转换)
  • @Dr.Gut 标准布局类不能有多个基类,除非它们都是空的(或者一个非空且派生类为空)
猜你喜欢
  • 2016-12-28
  • 2010-11-16
  • 2015-09-24
  • 1970-01-01
  • 2014-09-03
  • 2013-01-25
  • 1970-01-01
  • 1970-01-01
  • 2020-07-05
相关资源
最近更新 更多