Related: How does =delete on destructor prevent stack allocation?
First, I realize this sort of thing is playing with fire, tempting UB. I'm asking to get a better understanding of corner-cases of union
s.
As I understand it, if I have a class with a c'tor and d'tor and put it in a union
, the compiler will force me to tell it what to do about construction and destruction. As in
#include <iostream>
struct S {
int x;
S() : x(-1) { std::cout << "S()" << std::endl; }
~S() { std::cout << "~S()" << std::endl; }
static S& instance() {
union DoNotDestruct {
S value;
DoNotDestruct() : value() {}
~DoNotDestruct() {}
};
static DoNotDestruct inst;
return inst.value;
}
};
int main() {
return S::instance().x;
}
which just reports construction, not destruction.
However, it appears that if I have a deleted destructor, that doesn't work; I have to use placement-new
:
#include <new>
// Placement-new version:
struct S0 {
S0() {}
~S0() = delete;
static S0& instance() {
union DoNotDestruct {
S0 value;
DoNotDestruct() {
// I believe value isn't constructed yet.
new (&value) S0; // Placement-new.
}
~DoNotDestruct() {} // Omit S0's d'tor.
};
static DoNotDestruct inst;
return inst.value;
}
};
// Init version:
struct S1 {
S1() {}
~S1() = delete;
static S1& instance() {
union DoNotDestruct {
S1 value;
DoNotDestruct() : value{} {} // Why does this line want S1::~S1() to exist?
~DoNotDestruct() {} // Omit S1's d'tor.
};
static DoNotDestruct inst;
return inst.value;
}
};
https://godbolt.org/z/7r4ebszor
Here, S0
is happy, but S1
complains on the constructor line that S1::~S1()
is deleted. Why?
I tried adding noexcept
on the thought that in a class
, if you have multiple members, the 0th member needs to be destructible if any subsequent c'tors throw. It looks like even a class with a deleted d'tor can't itself hold an indestructible member even if the c'tors are all noexcept
. It gives the same error as the union
: https://godbolt.org/z/jx3W1YEPf
[class.dtor]/15 says that a program is ill-formed if a potentially invoked destructor is defined as deleted.
[class.base.init]/12 says that a destructor of a potentially constructed subobject of a class is potentially invoked in a non-delegating constructor.
[special]/7 says that all non-static data members are potentially constructed subobjects, although the sentence is qualified a bit weirdly I think ("For a class, [...]").
I don't see any exceptions in this for single-member classes, unions or noexcept
. So it seems to me that GCC is correct for S1
.
Clang is also the only compiler out of Clang, GCC, ICC and MSVC that doesn't reject this version.
For S0
however, this reasoning would also apply, making it ill-formed as well. However none of the four compilers agrees on that, so maybe I am wrong.
The quoted wording comes mostly from CWG issue 1424. Clang lists its implementation status currently as unknown (https://clang.llvm.org/cxx_dr_status.html), as does GCC (https://gcc.gnu.org/projects/cxx-dr-status.html).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With