Is the following snippet of code well-formed?
struct A { ~A() = delete; };
A *pa{new A{}};
class B { ~B() = default; };
B *pb{new B{}};
At a first glace it would seems as if the deleted dtor of A
and the private explicitly-defaulted dtor of B
are never used (intentional memory leak, if you will), which would arguably imply that it is well-formed.
Clang accepts the program for various compiler and C++ versions (C++11 through C++2a).
GCC, on the other hand, rejects the program for various compiler and C++ versions.
struct A { ~A() = delete; }; A *pa{new A{}}; // GCC error: use of deleted function 'A::~A()' class B { ~B() = default; }; B *pb{new B{}}; // GCC error: 'B::~B()' is private within this context
(It if well-formed; before I file a bug report: Is there any open GCC bug report for this corner case? I've searched GCC:s bugzilla myself to no avail.)
Peculiarly GCC accepts the case of a private user-provided destructor:
class C { ~C(); };
C *pc{new C{}}; // OK
class D { ~D() {} };
D *pd{new D{}}; // OK
which could hint for some GCC doing something special for aggregate classes, as A
and B
are aggregates whereas C
and D
are not. However GCC is no consistent with this behaviour, as the following example
struct E {
~E() = delete;
private:
int x;
};
E *pe{new E{}};
where E
is not an aggregate (private data member) is likewise rejected like the for the aggregate classes A
and B
above, whereas the examples of F
and G
below (an aggregate until C++20, and a non-aggregate, respectively)
struct F {
F() = default;
~F() = delete;
};
F *pf{new F{}};
struct G {
G() = default;
~G() = delete;
private:
int x;
};
G *pg{new G{}};
are both accepted by GCC.
To begin with, [class.dtor]/4 explicitly mentions that the selected destructor for a given class may be deleted:
At the end of the definition of a class, overload resolution is performed among the prospective destructors declared in that class with an empty argument list to select the destructor for the class, also known as the selected destructor. [...] Destructor selection does not constitute a reference to, or odr-use ([basic.def.odr]) of, the selected destructor, and in particular, the selected destructor may be deleted ([dcl.fct.def.delete]).
[class.dtor]/15 governs for which cases a destructor is invoked implicitly; starting with the first part:
A destructor is invoked implicitly
- (15.1) for a constructed object with static storage duration ([basic.stc.static]) at program termination ([basic.start.term]),
- (15.2) for a constructed object with thread storage duration ([basic.stc.thread]) at thread exit,
- (15.3) for a constructed object with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl]),
- (15.4) for a constructed temporary object when its lifetime ends ([conv.rval], [class.temporary]).
[...] A destructor may also be invoked implicitly through use of a delete-expression ([expr.delete]) for a constructed object allocated by a new-expression ([expr.new]); the context of the invocation is the delete-expression.
None of (15.1) through (15.4) applies here, and particularly "for a constructed object allocated by a new-expression", which does apply here, a destructor is invoked implicitly only through the use of a delete-expression, which we are not using in this example.
The second part of [class.dtor]/15 covers when a destructor is potentially invoked, and that a program is ill-formed if a destructor is potentially invoked and is deleted:
A destructor can also be invoked explicitly. A destructor is potentially invoked if it is invoked or as specified in [expr.new], [stmt.return], [dcl.init.aggr], [class.base.init], and [except.throw]. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.
No destructor is invoked explicitly in this case.
[expr.new] (particularly referring to [expr.new]/24) does not apply here, as it relates only to when creating an array of objects of class type (using a new-expression).
[stmt.return] does not apply here, as it relates to (possible) invocation of constructors and destructors in return statements.
[dcl.init.aggr] (particularly [dcl.init.aggr]/8) does not apply here as it relates to the potentially invoked destructors of the elements of the aggregate, and not a potentially invoked destructor of the aggregate class itself.
[class.base.init] does not apply here as it relates to potentially invoked destructors of (base class) subobjects.
[except.throw] (particularly [except.throw]/3 and [except.throw]/5) does not apply here as it relates to the potentially invoked destructor of an exception object.
Thus, none of [class.dtor]/15 applies in this case, and GCC is wrong to reject the examples of A
, B
and E
in the OP. As pointed out in a comment by @JeffGarrett, this looks like the following open GCC bug report:
And we may note, as pointed out in the bug report, that GCC only wrongly rejects these program when allocating using list-initialization, whereas the following modified examples A
, B
and E
are all accepted by GCC:
struct A { ~A() = delete; };
A *pa{new A()};
class B { ~B() = default; };
B *pb{new B()};
struct E {
~E() = delete;
private:
int x;
};
E *pe{new E()};
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