dfr*_*fri 6 c++ gcc language-lawyer compiler-bug
以下代码片段格式正确吗?
struct A { ~A() = delete; };
A *pa{new A{}};
class B { ~B() = default; };
B *pb{new B{}};
Run Code Online (Sandbox Code Playgroud)
A乍一看,B似乎从未使用过的已删除 dtor和私有显式默认 dtor从未使用过(有意的内存泄漏,如果您愿意的话),这可以说是格式良好的。
Clang 接受各种编译器和 C++ 版本(C++11 到 C++2a)的程序。
另一方面,GCC 拒绝针对各种编译器和 C++ 版本的程序。
Run Code Online (Sandbox Code Playgroud)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
(如果格式正确;在我提交错误报告之前:是否有针对此极端情况的任何开放的 GCC 错误报告?我自己搜索了 GCC:s bugzilla 无济于事。)
特别是 GCC 接受私有用户提供的析构函数的情况:
class C { ~C(); };
C *pc{new C{}}; // OK
class D { ~D() {} };
D *pd{new D{}}; // OK
Run Code Online (Sandbox Code Playgroud)
这可能暗示某些 GCC 为聚合类做一些特殊的事情,因为A和B是聚合而C和D不是。但是 GCC 与此行为不一致,如下例所示
struct E {
~E() = delete;
private:
int x;
};
E *pe{new E{}};
Run Code Online (Sandbox Code Playgroud)
其中E一个非集合(私人数据成员)同样拒绝像为聚合类A和B以上,而的实施例F和G下面(的集合体,直到C ++ 20,和非聚集体,分别地)
struct F {
F() = default;
~F() = delete;
};
F *pf{new F{}};
struct G {
G() = default;
~G() = delete;
private:
int x;
};
G *pg{new G{}};
Run Code Online (Sandbox Code Playgroud)
都被 GCC 接受。
首先,[class.dtor]/4明确提到可以删除给定类的选定析构函数:
在类定义结束时,在该类中声明的预期析构函数之间执行重载决策,并使用空参数列表来选择该类的析构函数,也称为选定的析构函数。[...]析构函数选择不构成对所选析构函数的引用或odr 使用([basic.def.odr]) ,特别是,可以删除所选析构函数([dcl.fct.def 。删除])。
[class.dtor]/15控制在哪些情况下隐式调用析构函数;从第一部分开始:
隐式调用析构函数
- (15.1)对于在程序终止([basic.start.term])时具有静态存储持续时间([basic.stc.static])的构造对象,
- (15.2)对于线程退出时具有线程存储持续时间 ([basic.stc.thread]) 的构造对象,
- (15.3)对于具有自动存储持续时间的构造对象([basic.stc.auto]),当创建对象的块存在时([stmt.dcl]),
- (15.4)对于构造的临时对象,当其生命周期结束时([conv.rval],[class.temporary])。
[...] 析构函数也可以通过使用 new表达式([expr.new])分配的构造对象的删除表达式([expr.delete]) 隐式调用;调用的上下文是删除表达式。
(15.1)到(15.4)都不适用于此,特别是“对于由new 表达式分配的构造对象”,这确实适用于此,仅通过使用delete 表达式隐式调用析构函数,我们是本例中未使用。
[class.dtor]/15的第二部分涵盖了析构函数何时可能被调用,以及如果析构函数可能被调用并被删除,则程序是格式错误的:
析构函数也可以被显式调用。如果析构函数被调用或在 [expr.new]、[stmt.return]、[dcl.init.aggr]、[class.base.init] 和 [ except.throw] 中指定,则可能会调用该析构函数。 如果可能调用的析构函数被删除或无法从调用上下文访问,则程序是格式错误的。
在这种情况下,不会显式调用析构函数。
[expr.new](特别是指[expr.new]/24)此处不适用,因为它仅与创建类类型的对象数组时相关(使用new-expression)。
[stmt.return]在这里不适用,因为它涉及 return 语句中构造函数和析构函数的(可能)调用。
[dcl.init.aggr](特别是[dcl.init.aggr]/8)此处不适用,因为它涉及聚合元素的潜在调用的析构函数,而不是聚合类本身的潜在调用的析构函数。
[class.base.init]在这里不适用,因为它涉及(基类)子对象的潜在调用的析构函数。
[ except.throw](特别是[ except. throw] / 3和[ except. throw] / 5)在这里不适用,因为它涉及异常对象的潜在调用的析构函数。
因此,[class.dtor]/15 都不适用于这种情况,并且 GCC 拒绝OP 中A,B和的示例是错误的。正如@JeffGarrett 在评论中E指出的,这看起来像以下开放的 GCC 错误报告:
我们可能会注意到,正如错误报告中指出的那样,GCC 仅在使用列表初始化分配时错误地拒绝了这些程序,而以下修改后的示例A、B和E均被 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()};
Run Code Online (Sandbox Code Playgroud)