为什么如果它被删除则调用析构函数,如果没有删除则不调用它?

时间:2014-11-18 04:26:44

标签: c++ c++11 destructor

请考虑以下代码:

#include <iostream>

struct A 
{
    A(){ };
    ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }

DEMO

程序不会产生任何输出,这意味着不会调用析构函数。但是如果我们用delete说明符替换析构函数的主体,程序甚至都不会编译。

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

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

int main(){ }

DEMO

由于调用了删除的功能。那是在这种情况下被调用的析构函数。为什么会有这样的差异?

即使我们明确定义B的构造函数,它也不会起作用:

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A 
{ 
    B(){ };
};

B *b = new B;

int main(){ }

DEMO

6 个答案:

答案 0 :(得分:16)

问题是编译器删除了B的构造函数,否则默认定义将是错误的。这是因为A没有析构函数,如果B无法销毁,B的默认构造函数无法从A构造A。如果用g ++编译,那就是你得到的错误:

note: 'B::B()' is implicitly deleted because the default definition would be ill-formed:

或clang ++:

error: call to implicitly-deleted default constructor of 'B'

如果您明确声明构造函数B(){},那么它会因为删除A而无法销毁B A::~A()部分而抱怨。 在编译时检查B隐藏其父A的能力,因此您收到错误。

+1的问题。看起来你不能继承(然后使用一个实例)来自具有已删除的析构函数的类,尽管这是我第一次碰到这个问题。

答案 1 :(得分:15)

标准的适用部分是(引用N4140):

§12.4[class.dtor] / p11:

  

如果调用析构函数或指定了析构函数,则可能会被调用   在5.3.4和12.6.2中。如果是析构函数,程序就会形成错误   可能被调用的内容被删除或无法从上下文访问   调用。

§12.6.2[class.base.init] / p10:

  

在非委托构造函数中,每个潜在的析构函数   可能会调用构造的类类型的子对象(12.4)。 [   注意:此规定确保在抛出异常时可以为完全构造的子对象调用析构函数(15.2)。    - 结束记录]

§12[特别] / p5:

  

对于一个类,它的非静态数据成员,它的非虚拟直接基础   类,如果类不是抽象的(10.4),它的虚拟基础   类被称为可能构建的子对象

由于AB的非虚拟直接基础,因此是可能构建的B子对象,因此其析构函数可能被调用<{em>}在B::B()中,并且由于该析构函数被删除,因此该程序格式不正确。

另见CWG issue 1424

答案 2 :(得分:11)

关于我认为你的基本问题:为什么你不能建构 一个B,即使它只是A析构函数 exists:在B的构造函数中,编译器自动生成 如果存在异常,则调用A的析构函数的代码。如果是A 要用作基类,必须具有可访问的析构函数 (公共的或受保护的)。

在你的情况下,当然,B的构造函数不能抛出,等等 A::~A()永远不会被调用。但编译器不能总是 确定是否是这种情况,标准不要求 它甚至尝试。编译器必须假设B::B()的主体可以 抛出(在完全构造A之后)。即使它可以 确定B::B()不能抛出,并且不生成代码 调用A的析构函数,这是一个优化,但不是 允许改变代码的合法性。

答案 3 :(得分:5)

基于第一个代码:

#include <iostream>
struct A
{
  A(){ };
  ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }
  1. 对象b被声明为全局,因此它的生命周期与程序运行一样长。
  2. 对象b是动态分配的,因此需要“裸”删除。
  3. 尝试以下方法:

    #include <iostream>
    
    struct A 
    {
        A(){ };
        ~A(){ std::cout << "~A::A()" << std::endl; };
    };
    
    struct B: A { };
    
    int main(){
        B b;
    }
    

答案 4 :(得分:2)

C ++ 14对此很清楚:

  

[C++14: 12.6.2/10]:在非委托构造函数中,可能会调用每个可能构造的类类型子对象的析构函数(12.4)。 [注意:此规定确保在抛出异常时可以为完全构造的子对象调用析构函数(15.2)。 -end note]

C ++ 11中没有it was added in issue 1424这样的措辞,但由于这个事实不容忽视,所以在C ++ 11和C ++ 98/03实现中也是如此。

所以,虽然你仍然没有调用析构函数,但继承的存在意味着B()需要~A()可以调用:基本上,这意味着< em>可访问未删除

答案 5 :(得分:0)

回答你的标题问题:如果你不调用delete那么就不会发生删除,尽管对象使用的内存可以通过终止创建它的程序来释放,这可能会导致要做的几件事之一:

  • 操作系统将内存标记为空闲/可用,因为拥有进程已终止,然后内存可再次供操作系统/其他进程使用
  • 调用所有对象的析构函数,无论是隐式析构函数还是显式声明,因为主进程终止(然后内存再次可用于OS /其他进程)。
  • 来自原始代码的纯粹意志力超越了编程功能,并删除了未删除对象分配的内存内容,并将内存返回给操作系统。

嗯,除了最后一个,你得到了要点,最后一个只是一个喜欢多愁善感加强程序员自我:D

对此唯一的限制因素是孤立对象(其中指向内存位置的指针在代码中的某个点丢失,see here for example)和具有已删除析构函数的对象(see here for a brief explanation):这些不能删除因为在第一个实例中它们不再是可寻址的,而在第二个实例中它们是一个错误的*对象,因为它们没有析构函数(*错误,因为不符合标准用途/规范,但有时候当您可能想要在拥有进程终止之前/在某个特定情况singleton之前阻止某个对象被自己删除时,但是,良好的编程/逻辑应该可以防止对析构函数完全不可用的需要)。

如果您需要更多信息,或者上述任何需要澄清,请告诉我,我将非常乐意为您提供帮助:)