c++ - 构造(但不破坏)具有已删除或非用户提供的私有(private)析构函数的类的对象

标签 c++ gcc language-lawyer compiler-bug

以下代码片段格式正确吗?

struct A { ~A() = delete; };
A *pa{new A{}};

class B { ~B() = default; };
B *pb{new B{}};
乍一看,似乎 A 的已删除 dtor和 B 的私有(private)显式默认 dtor从未使用过(故意的内存泄漏,如果你愿意的话),这可能意味着它是格式良好的。
Clang 接受适用于各种编译器和 C++ 版本(C++11 到 C++2a)的程序。
另一方面,GCC 拒绝各种编译器和 C++ 版本的程序。
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 接受用户提供的私有(private)析构函数的情况:
class C { ~C(); };
C *pc{new C{}};  // OK

class D { ~D() {} };
D *pd{new D{}};  // OK
这可能暗示某些 GCC 为聚合类做一些特殊的事情,如 AB是聚合,而 CD不是。但是 GCC 与此行为不一致,如下例所示
struct E {
    ~E() = delete; 
private: 
    int x;
};
E *pe{new E{}};
在哪里 E不是聚合(私有(private)数据成员)同样被拒绝,如聚合类 AB上面,而 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{}};
都被 GCC 接受。

最佳答案

片段格式正确;这是一个 GCC 错误 (59238)
首先,[class.dtor]/4明确提到可以删除给定类的选定析构函数:

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控制在哪些情况下隐式调用析构函数;从第一部分开始:

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.


(15.1) 到 (15.4) 中的任何一个都不适用于此处,尤其是“对于由 new 表达式分配的构造对象”,它确实适用于此处,仅通过使用删除表达式隐式调用析构函数,我们是在这个例子中没有使用。
[class.dtor]/15第二部分涵盖何时可能调用析构函数,以及如果可能调用并删除析构函数,则程序格式错误:

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.


在这种情况下,没有显式调用析构函数。
[expr.new] (特别是指 [expr.new]/24 )不适用于此处,因为它仅与创建类类型对象数组时有关(使用新表达式)。
[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 拒绝 A 的示例是错误的。 , BE在OP中。正如所指出的 in a comment by @JeffGarrett ,这看起来像以下打开的 GCC 错误报告:
  • Bug 59238 - Dynamic allocating a list-initialized object of a type with private destructor fails.

  • 我们可能会注意到,正如错误报告中指出的那样,GCC 在使用列表初始化分配时只会错误地拒绝这些程序,而以下修改示例 A , BE都被 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()};
    

    关于c++ - 构造(但不破坏)具有已删除或非用户提供的私有(private)析构函数的类的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65124761/

    相关文章:

    c++ - 在 Visual Studio 中使用 'Linux development with C++' 时如何将源保留在本地?

    c++ - 如何使用 Levenshtein 距离字符串度量

    c++ - gcc 3.3.3 支持预编译头文件吗?

    c++ - 使用 using 声明时,非限定名称查找如何工作?

    c++ - 当简单捕获中的标识符显示为参数的声明符 ID 时,没有编译器诊断

    c++ - 类型别名和相似类型

    c++ - 在 Visual Studio 中链接到版本中的库和调试中的 .exe 崩溃

    c - FLEX- Bison : error: expected identifier or ‘(’ before string constant

    c++11 - 为什么std::chrono::time_point的大小不足以存储struct timespec?

    c++ - GCC 在要求此引用声明的 constexpr 说明符方面是否正确?