reinterpret_cast 清空 child class

reinterpret_cast to empty child class

可能这个问题被多次提出,但我仍然找不到任何合理的合理答案。考虑以下代码片段:

struct A {virtual int vfunc() = 0;};
struct B {virtual ~B() {}};
struct C {void *cdata;};
//...
struct Z{};

struct Parent:
  public A,
  virtual B,
  private C,
  //...
  protected Z
{
  int data;
  virtual ~Parent(){}
  virtual int vfunc() {return 0;} // implements A::vfunc interface
  virtual void pvfunc() {};
  double func() {return 0.0;}
  //...etc
};

struct Child:
  public Parent
{
  virtual ~Child(){}
  int more_data;
  virtual int vfunc() {return 0;} // reimplements A::vfunc interface
  virtual void pvfunc() {};// implements Parent::pvfunc interface
};

template<class T>
struct Wrapper: public T 
{
 // do nothing, just empty
};

int main()
{
  Child ch;
  Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);
  wr.data = 100;
  wr.more_data = 200;
  wr.vfunc();
  //some more usage of wr...
  Parent pr = wr;

  pr.data == wr.data; // true?
  //...

  return 0;
}

基本上,这显示了对虚拟 child class Wrapper 的引用及其祖先 类.

成员的用法

问题是:此代码是否符合标准?如果不是那么 它到底违反了什么

PS:请不要提供 "this is wrong on so many levels omg" 之类的答案。我需要标准中的确切引述来证明这一点。

我当然希望这是您作为学术练习所做的事情。请不要以任何方式编写任何类似于此的任何真实代码。我不可能指出这段代码的所有问题,因为这里几乎所有内容都存在问题。

但是,要回答真正的问题 - 这是完全未定义的行为。在 C++17 中,它是第 8.2.10 节 [expr.reinterpret.cast]。使用括号中的短语获取以前标准的相关部分。


EDIT 我认为一个简洁的答案就足够了,但已要求提供更多详细信息。其他的代码问题我就不提了,因为它们只会把水搅浑。

这里有几个关键问题。让我们关注 reinterpret_cast.

Child ch;
Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);

规范中的大部分措辞都使用了指针,因此基于8.2.10/11,我们将示例代码稍微更改为这样。

Child ch;
Wrapper<Child> *wr = reinterpret_cast<Wrapper<Child>*>(&ch);

这里引用了标准的一部分来证明这一点。

A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast(x) has the same effect as the conversion *reinterpret_cast(&x) with the built-in & and * operators (and similarly for reinterpret_cast(x)). — end note ] No temporary is created, no copy is made, and constructors (15.1) or conversion functions (15.3) are not called.

标准的一个微妙的小部分是 6.9.2/4,它允许在某些特殊情况下将指向一个对象的指针视为指向不同类型的对象。

Two objects a and b are pointer-interconvertible if:

(4.1) — they are the same object, or

(4.2) - one is a standard-layout union object and the other is a non-static data member of that object (12.3), or

(4.3) — one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object (12.2), or

(4.4) — there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer- interconvertible.

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast (8.2.10). [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]

但是,您的情况不符合此条件,因此我们不能使用此异常将指向 Child 的指针视为指向 Wrapper<Child>.[=19= 的指针]

我们将忽略关于 reinterpret_cast 的内容,这些内容不涉及两个指针类型之间的转换,因为这种情况只涉及指针类型。

注意8.2.10/1的最后一句

Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.

后面有 10 个段落。

第 2 段说 reinterpret_cast 不能抛弃常量。不关我们的事。

第 3 段说结果可能会或可能不会产生不同的表示。

第 4 段和第 5 段是关于指针和整数类型之间的转换。

第 6 段是关于转换函数指针的。

第8段是关于函数指针和对象指针之间的转换。

第 9 段是关于转换空指针值。

第 10 段是关于成员指针之间的转换。

上面引用了第 11 段,基本上说转换引用类似于转换指针。

剩下第 7 段,其中指出。

An object pointer can be explicitly converted to an object pointer of a different type.73 When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast(static_cast(v)). [ Note: Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. — end note ]

这意味着我们可以整天在这两种指针类型之间来回转换。但是,这就是我们可以安全地做的所有事情。你做的不止于此,是的,有一些例外允许一些其他事情。

这里是 6.10/8

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

(8.1) — the dynamic type of the object,

(8.2) — a cv-qualified version of the dynamic type of the object,

(8.3) — a type similar (as defined in 7.5) to the dynamic type of the object,

(8.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object,

(8.5) — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

(8.6) — an aggregate or union type that includes one of the aforementioned types among its elements or non- static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),

(8.7) — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

(8.8) — a char, unsigned char, or std::byte type.

你的情况不满足任何一个。

在你的例子中,你正在使用一个指向一种类型的指针,并迫使编译器假装它指向另一种类型。这两者在您眼中看起来有多重要 - 您是否知道完全符合标准的编译器不必将派生 class 的数据放在基 class 的数据之后?这些细节不是 C++ 标准的一部分,而是编译器实现的 ABI 的一部分。

事实上,很少有使用 reinterpret_cast 的情况,除了携带一个指针然后将其转换回不会引发未定义行为的原始类型之外。

如另一个答案所述,此讨论涉及部分 C++17 标准的 8.2.10 [expr.reinterpret.cast]。

本节第 11 句解释了对于引用 objects 我们可以有与指向指针相同的推理 对象。

Wrapper<Child> &wr = reinterpret_cast<Wrapper<Child>&/**/>(ch);
or
Wrapper<Child> *wr = reinterpret_cast<Wrapper<Child>*/**/>(&ch);

本节第7句解释了对于指向对象的指针 reinterpret_cast可以看成两个static_cast依次排列 (通过 void *)。

在这个问题的特定情况下,类型 Wrapper<Child> 实际上继承自 Child,所以单个 static_cast 应该 足够(不需要两个 static_cast,也不需要 reinterpret_cast)。

所以如果reinterpret_cast在这里可以看作是a的组合 通过 void * 和正确的 static_cast,无用 static_cast, 它应该被认为等同于这个正确的 static_cast.


哼哼...

转念一想,我想我完全错了
(static_cast不正确,我看错了)

如果我们有

Wrapper<Child> wc=...
Child *pc=&wc;
Wrapper<Child> *pwc=static_cast<Wrapper<Child>*>(pc);

static_cast(然后是 reinterpret_cast)是正确的 因为它 返回 到原始类型。

但在你的示例中,原始类型不是 Wrapper<Child>Child.
即使这种可能性很小,也没有什么可以禁止编译器 在 Wrapper<Child>.
中添加一些隐藏的数据成员 Wrapper<Child> 不是空结构,它参与 在具有动态多态性的层次结构中,任何解决方案都可以 由编译器在后台使用。
所以,在 reinterpret_cast 之后,它变成了未定义的行为,因为 存储在指针(或引用)中的地址将指向 一些字节的布局为 Child 但以下代码 将这些字节与 Wrapper<Child> 的布局一起使用 可能不同。