为什么Destructor称为三次?

时间:2015-09-05 08:32:09

标签: c++

我正在研究RVO / Copy-Constructor / Destructor并随机检查代码。我有点困惑,为什么析构函数称为三次.. ??

#include <iostream>
using namespace std;
class A{
    public:
        A(){
            cout << "Simple Constructor" << endl;
        }
        A(const A& obj){
            cout << "Copy Constructor " << endl;
        }
        A operator =(A obj){
            cout << "Assignment Operator" << endl;
        }
        ~A(){
            cout << "Destructor " << endl;
        }       
};
A fun(A &obj){
    cout << "Fun" << endl;
    return obj;
}
int main(){
    A obj;
    obj=fun(obj);
    cout << "End" << endl;
    return 0;
}

输出:

Simple Constructor // ok
Fun // ok
Copy Constructor // ok for =
Assignment Operator // ok
Destructor // ok for =
Destructor // why here destructor called?
End // ok
Destructor // ok for main

我期待Destructor被召唤两次。

一个用于(=) operator's对象。

int main()'s对象的第二个。

为什么第三次被召唤?怎么样?

5 个答案:

答案 0 :(得分:3)

     A operator =(A obj){
        cout << "Assignment Operator" << endl;
    }

您将函数A::operator=声明为具有返回类型(在这种情况下为A的实例),但实现没有return语句。 这是未定义的行为

您实质上是在询问对未定义行为的响应。答案是&#34;任何事情都是&#34;。在您的情况下,使用编译器和系统,您将再次调用析构函数而不是构造函数。您很幸运,您的计划没有创建nasal demons或删除您的硬盘。

解决方案很简单:不要调用未定义的行为。编写复制赋值运算符的规范方法是使返回类型成为对类的引用并返回*this

     A& operator =(A obj){
        cout << "Assignment Operator" << endl;
        return *this;
    }

通过此更正,您将看到对各种构造函数的调用以及对析构函数余额的调用。

假设您使用了非标准的复制赋值运算符(但使用了正确编写的return语句):

     A operator =(A obj){
        cout << "Assignment Operator" << endl;
        return *this;
    }

再一次,你会看到对构造函数和析构函数的调用。您将看到多少个调用取决于您的编译器和优化级别。

答案 1 :(得分:2)

来自N4527的

[stmt.return] / p2

  

离开函数末尾相当于没有返回   值;这会导致值返回时出现未定义的行为   功能

调用extra-destructor来释放未初始化的对象 - Undefined Behavior

这也是clang / MSVC首先不接受您的代码的原因。 Gcc会发出警告。

详细说明:

虽然您说operator=按值返回一个,但您没有返回任何对象。这意味着跳过初始化并将其视为完全成熟的对象。

以下是gcc应该采用的方式(实际接受代码并产生输出的唯一编译器):

Simple Constructor // Construct obj in main
Fun // Calls the function
Copy Constructor // To construct operator='s argument directly (RVO elision here)
Assignment Operator // Assignment operator
(assignment doesn't return anything and gcc accepts it - so nop here)
Destructor // Destroy operator='s temporary (the parameter)
Destructor // Destroy the UNINITIALIZED object allocated for the result of operator=
End
Destructor // obj in main

答案 2 :(得分:1)

您正在按值传递obj,并在以下函数中按值返回:

A operator =(A obj){
    cout << "Assignment Operator" << endl;
    return *this;
}

为避免额外复制,您应将功能更改为:

A& operator =(const A& obj){
    cout << "Assignment Operator" << endl;
    return *this;
}

答案 3 :(得分:0)

它看起来就像是因为你没有从赋值运算符返回一个对象,但调用者必须销毁所谓的返回值。令我惊讶的是,编译器将允许这个(或者当应该构造返回的对象时语言允许它。)

所以被破坏的对象是:

  1. 从赋值运算符
  2. 返回的临时对象
  3. 发送给赋值运算符的临时对象
  4. main
  5. 中定义的原始对象

    构造的对象是:

    1. 发送给赋值运算符的临时对象
    2. main
    3. 中定义的原始对象

      将赋值运算符定义为返回void或实际返回obj*this将消除此差异。

答案 4 :(得分:0)

三个析构函数调用的可能原因可能是,

  1. 您正在将本地对象传递给赋值运算符,因此在函数执行停止时将其删除
  2. obj = fun(obj),在这里你要为&#34; obj&#34;分配一个新对象,所以旧的对象必须删除
  3. 最后一个是&#34; obj&#34;当主要功能结束时,它本身就会被删除。
  4. 希望它会做!!