cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[class.dtor] Does a non-virtual call of destructor of a base class end the lifetime of the most derived object? #477

Open frederick-vs-ja opened 7 months ago

frederick-vs-ja commented 7 months ago

Full name of submitter (unless configured in github; will be published with the issue): Gee Law (to be confirmed)

Reference (section label): [class.dtor]

Link to reflector thread (if any):

Issue description:

Currently, mainstream implementations suggest that calling pz->id2() after destroying the Y base class subobject in the following example has undefined behavior.

#include <cstdio>

void * volatile px{};

struct X {
  virtual char id1() const { return 'X'; }
  ~X() { std::putchar(static_cast<X*>(px)->id1()); } // intentionally non-virtual
};

struct Y : X {
  char id1() const override { return 'Y'; }
};

struct Z : Y {
  char id1() const override { return 'Z'; }
  virtual char id2() const { return 'Z'; }
};

struct W : Z {
  char id1() const override { return 'W'; }
  char id2() const override { return 'W'; }
};

union WBuf {
  WBuf() : obj{} {}
  ~WBuf() {}

  W obj;
};

int main() {
  WBuf wb{};
  auto pz = static_cast<Z*>(&wb.obj);
  px = static_cast<X*>(pz);
  static_cast<Y*>(pz)->Y::~Y();
  // Are the wb.obj most derived object and its Z base class subobject still living after the previous line?
  std::putchar(pz->id2()); // Likely UB: segmentation fault with GCC 13 and Clang 17 w/ or w/o -O2.
}

Per [class.dtor] p18, it's clear that the lifetime of the Y base class subobject (and its subobjects) ends after the destructor call. But the status is unclear for the most derived object (wb.obj) and the Z base class subobject. It doesn't seem reasonable to keep them living with the vptr (or any needed metadata) modified or destroyed.

@GeeLaw suggested that in there situation where a destructor call ends the lifetime of an object ( "Once a destructor is invoked for an object, the object's lifetime ends"), there can be multiple such destructors for an object of a derived class type and invoking any of them ends the lifetime of the most derived object[^1]. However, it's unclear whether such reading is correct, and others may think that there's at most one such destructor for each object.

It is also unclear whether the situation differs when virtual functions or virtual base classes are not involved (i.e. no metadata needed).

[^1]: Originally posted here in Chinese.

Suggested resolution:

jensmaurer commented 6 months ago

I think this is addressed by CWG2839.