为什么我允许声明一个删除了析构函数的对象?

时间:2014-11-18 12:49:06

标签: c++ c++11 language-lawyer

考虑以下文字:

  

[C++11: 12.4/11]:隐式调用析构函数

     
      
  • 用于在程序终止时具有静态存储持续时间(3.7.1)的构造对象(3.6.3),
  •   
  • 用于在线程出口处具有线程存储持续时间(3.7.2)的构造对象,
  •   
  • 对于具有自动存储持续时间(3.7.3)的构造对象,当创建对象的块退出时(6.7),
  •   
  • 用于临时对象的生命周期结束时构造的临时对象(12.2),
  •   
  • 对于由 new-expression (5.3.4)分配的构造对象,通过使用 delete-expression (5.3.5),
  •   
  • 在几种情况下由于处理异常(15.3)。
  •   
     

如果声明了类类型的对象或其数组,并且在声明时无法访问类的析构函数,则程序格式不正确。也可以显式调用析构函数。

那为什么这个程序编译成功?

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete;
};

A* a = new A;

int main() {}

// g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

海湾合作委员会是否只是宽容?


我倾向于这么说,因为它拒绝了以下the standard appears to have no particular rule specific to deleted destructors in an inheritance hierarchy(唯一松散相关的措辞与默认默认构造函数的生成相关):

#include <iostream>

struct A 
{
    A() {};
    ~A() = delete;
};

struct B : A {};

B *b = new B; // error: use of deleted function

int main() {}

4 个答案:

答案 0 :(得分:13)

第一部分不是格式错误,因为标准文本不适用 - 在那里没有声明类型A的对象。

对于第二部分,让我们回顾一下对象构建的工作原理。标准说(15.2 / 2)如果施工的任何部分抛出,那么到目前为止所有完全建造的子对象都会以相反的构造顺序被破坏。

这意味着构造函数的基础代码,如果全部手工编写,将看起来像这样:

// Given:
struct C : A, B {
   D d;
   C() : A(), B(), d() { /* more code */ }
};

// This is the expanded constructor:
C() {
  A();
  try {
    B();
    try {
      d.D();
      try {
        /* more code */
      } catch(...) { d.~D(); throw; }
    } catch(...) { ~B(); throw; }
  } catch(...) { ~A(); throw; }
}

对于更简单的类,默认构造函数的扩展代码(new表达式需要其定义)如下所示:

B::B() {
  A();
  try {
    // nothing to do here
  } catch(...) {
    ~A(); // error: ~A() is deleted.
    throw;
  }
}

对于某些子对象的初始化完成后可能无法抛出异常的情况,使其工作太复杂,无法指定。因此,这实际上并没有发生,因为B的默认构造函数首先被隐式定义为已删除,这是由于N3797 12.1 / 4中的最后一个项目符号点:

  

如果出现以下情况,则将类X的默认默认构造函数定义为已删除:

     
      
  • [...]
  •   
  • 任何直接或虚拟基类或非静态数据成员都具有从默认默认构造函数中删除或无法访问的析构函数的类型。
  •   

复制/移动构造函数的等效语言是12.8 / 11中的第四个项目符号。

12.6.2 / 10中还有一个重要的段落:

  

在非委托构造函数中,可能会调用每个直接或虚拟基类以及类类型的每个非静态数据成员的析构函数。

答案 1 :(得分:3)

B的析构函数是由编译器在错误行生成的,并且它调用了A的析构函数,该函数被删除,因此出错。在第一个例子中,没有任何东西试图调用A的析构函数,因此没有错误。

答案 2 :(得分:3)

我猜这就是发生的事情。

隐式生成的B()构造函数首先构造其类型为A的基类子对象。然后,该语言指出,如果在执行B()构造函数的主体期间抛出异常,则必须销毁A子对象。因此需要访问已删除的~A() - 当构造函数抛出时,正式需要它。当然,由于B()生成的主体是空的,这种情况永远不会发生,但~A()应该可访问的要求仍然存在。

当然,这只是1)从我的方面猜测为什么首先出现错误2)并没有以任何方式引用标准来说明这是否实际上是正式格式错误或只是gcc中的一个实现细节。也许可以给你一个线索,看看标准在哪里看......

答案 3 :(得分:1)

Accessibility is orthogonal to deletedness

  

[C++11: 11.2/1]:如果使用public访问说明符将一个类声明为另一个类的基类(第10节),则基类的public成员可以作为{ {1}}派生类的成员和基类的public成员可以作为派生类的protected成员访问。如果使用protected访问说明符将类声明为另一个类的基类,则基类的protectedpublic成员可以作为protected成员访问派生类。如果使用protected访问说明符将类声明为另一个类的基类,则基类的privatepublic成员可以作为protected成员访问派生类。

有这个:

  

private隐式或显式地引用已删除函数的程序,除了声明它之外,都是格式错误的。 [注意:这包括隐式或显式调用函数并形成指向函数的指针或指向成员的指针。它甚至适用于未进行潜在评估的表达式中的引用。如果函数重载,则仅在通过重载决策选择函数时才引用它。 -end note]

但你从来没有&#34;指的是&#34;已删除的析构函数。

(我仍然无法解释为什么继承示例不能编译。)