Reputation: 63124
Given the following code:
struct Bar {
Bar() { }
~Bar() { }
};
struct FooBase {
// No virtual destructor
};
struct Foo : FooBase {
Foo() : bar{} { }
union {
Bar bar;
};
};
int main() {
FooBase *p = new Foo;
static_cast<Foo *>(p)->bar.~Bar();
delete p;
}
I wish to detach Foo::bar
's lifetime from its enclosing Foo
.
As far as I know, delete p;
is well-defined if and only if Foo
's destructor is trivial.
Since Foo
only contains an union, and is not supposed to destruct its bar
itself, it looks like it is the case: informally speaking, that destructor does nothing.
But is this code actually well-defined according to the standard?
Upvotes: 3
Views: 684
Reputation: 24936
First of all, [expr.delete]
for me doesn't support your claim that "delete p;
is well-defined if and only if Foo's destructor is trivial".
In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.
Since the static type is FooBase
whereas the dynamic type is Foo
, I'd expect that to be UB.
Furthermore, (even in case this is not UB) I think that the destructor of Foo
isn't trivial.
[class.union]
A union-like class is a union or a class that has an anonymous union as a direct member. A union-like
class X
has a set of variant members. IfX
is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members ofX
.
Thus Foo
is a union-like class with bar
(of type Bar
which has a non-trivial destructor) being a variant member.
[class.dtor]
4) If a class has no user-declared destructor, a destructor is implicitly declared as defaulted [...].
5) A defaulted destructor for a
class X
is defined as deleted if
X
is a union-like class that has a variant member with a non-trivial destructor [...].
Therefore, I think that the implicitly defaulted destructor of Foo
should be defined as deleted and must be user-provided.
(Declare Foo q;
and see the compilation fail.)
A destructor is trivial if it is not user-provided and if
- [...] for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
I'd exepect that clause to extend to variant members as well since it doesn't say "non-variant data members".
You could have a virtual destructor in FooBase
and then implement ~Foo()
without including bar.~bar();
where it would be possible to destroy bar
in advance without it being destructed again in ~Foo()
.
I'd however strongly advise against it since you'd have to somehow call ~bar
for every static or dynamic Foo
before it goes out of scope.
Upvotes: 2