【问题标题】:Why Destructor called Three Time?为什么析构函数称为三次?
【发布时间】:2015-09-05 18:24:02
【问题描述】:

我正在研究 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 对象的第二个。

为什么叫第三次?以及如何?

【问题讨论】:

  • A operator =(A obj){ 获取一个临时副本,它的析构函数也被调用。签名应为A&amp; operator =(const &amp; A obj){。你也错过了return *this;
  • 因为输入不会改变。
  • @Drop 我在软件工程的第二学期,我们的老师没有深入地教我们。我只是在思考,工作,无法与任何人讨论。 :) 这就是我在这里的原因.. :) 并且书籍并没有提供所有要清除的内容。 :) 无论如何谢谢。
  • 1) 不是VS2015编译的。 2)修复错误后再次检查它,并彻底计算真实输出中的构造函数和您发布的构造函数

标签: c++


【解决方案1】:
     A operator =(A obj){
        cout << "Assignment Operator" << endl;
    }

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

您本质上是在询问对未定义行为的响应。答案是“任何事情都会发生”。在您的情况下,使用您的编译器和系统,您对析构函数的调用比对构造函数的调用多。你很幸运,你的程序没有创建nasal demons 或擦除你的硬盘。

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

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

通过此更正,您将看到对各种构造函数的调用和对析构函数的调用平衡。

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

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

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

【讨论】:

    【解决方案2】:

    [stmt.return]/p2 来自 N4527

    从函数的末尾流出相当于没有返回 价值;这会导致值返回中的未定义行为 功能。

    调用额外的析构函数来释放一个未初始化的对象 - 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
    

    【讨论】:

      【解决方案3】:

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

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

      为避免额外复制,您应该将函数更改为:

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

      【讨论】:

        【解决方案4】:

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

        所以被销毁的对象是:

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

        构造的对象是:

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

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

        【讨论】:

        • 我认为编译器在这种情况下只显示警告。您需要使用参数“-Wall”来 g++ 显示警告。
        【解决方案5】:

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

        1. 您将本地对象传递给赋值运算符,以便在函数执行停止时将其删除
        2. obj = fun(obj),这里给“obj”分配了一个新对象,所以旧的要删除
        3. 最后一个是“obj”本身在主函数结束时被删除。

        希望可以!!

        【讨论】:

          猜你喜欢
          • 2021-11-06
          • 2011-09-19
          • 2013-11-24
          • 2020-02-23
          • 2015-04-28
          • 1970-01-01
          • 2019-09-03
          • 2011-02-07
          相关资源
          最近更新 更多