【问题标题】:C++ Method chaining causes destructor to be called twiceC++ 方法链接导致析构函数被调用两次
【发布时间】:2016-10-14 12:07:57
【问题描述】:

我有一个简单的 c++ 程序,它使用方法链接,我注意到析构函数在仅用于链调用时被调用了两次。仅当链调用也包含构造函数时才会发生这种情况。如果单独调用析构函数,则只调用一次。

代码如下:

class Foo {
public:
    Foo()   { cout << "-- constructor " << this << endl; }
    ~Foo () { cout << "-- destructor  " << this << endl; };

    Foo& bar() {
        cout << "---- bar call  " << this << endl;
        return *this;
    }
};

int main() {
    cout << "starting test 1" << endl;
    {
        Foo f = Foo();
    }
    cout << "ending test 1"   << endl << endl;
    cout << "starting test 2" << endl;
    {
        Foo f = Foo().bar();
    }
    cout << "ending test 2"   << endl;
    return 0;
}

申请结果如下:

starting test 1
-- constructor 0x7ffd008e005f
-- destructor  0x7ffd008e005f
ending test 1

starting test 2
-- constructor 0x7ffd008e005f
---- bar call  0x7ffd008e005f
-- destructor  0x7ffd008e005f
-- destructor  0x7ffd008e005f
ending test 2

这是标准行为(如果是,为什么会这样?)还是我犯了一些错误?我可以防止这种情况吗?

【问题讨论】:

  • 添加复制构造函数和赋值运算符看看是怎么回事
  • 以下所有答案中提到的副本在第一种情况下是elided。如果您使用g++ -fno-elide-constructors 编译,您将看到调用了 2 个析构函数。
  • 嗯,这很有趣。谢谢!
  • 这不是问题,但不要使用std::endl,除非您需要它的额外功能。 '\n' 结束一行。

标签: c++ destructor method-chaining


【解决方案1】:

Foo f = Foo().bar(); 还调用Foo复制构造函数,这是当前编译器生成的,因此不会向控制台输出任何内容。 这就是为什么看起来你调用的析构函数多于构造函数。

您可以写const Foo&amp; f = Foo().bar(); 来避免复制。使用const 还可以延长匿名临时的生命周期,这很好。

【讨论】:

    【解决方案2】:

    请注意,该行涉及两个Foo 类型的对象:

    Foo f = Foo().bar();
        ^----------------this one
            ^------------and this one
    

    但是,一个是通过构造函数创建的,另一个是通过复制构造函数创建的。这就是为什么你只打印一条用于建筑而两条用于破坏的原因。代码一切正常,您只需实现复制构造函数即可看到一致的输出。

    【讨论】:

      【解决方案3】:

      还有一个你自己没有实现的构造函数。复制构造函数。

      你的电话Foo f = Foo().bar();可以写成Foo tmp = Foo(); Foo f = tmp.bar();

      只有 tmp 对象的实例化才会调用您的构造函数。 f 调用的构造函数是自动生成的复制构造函数。

      这应该会给你一些更好的输出:

      #include <iostream>
      using std::cout;
      using std::endl;
      
      class Foo {
      public:
          Foo() { cout << "-- constructor " << this << endl; }
          Foo(const Foo& f) { cout << "-- copy-constructor " << this << endl; }
          ~Foo() { cout << "-- destructor  " << this << endl; };
      
          Foo& bar() {
              cout << "---- bar call  " << this << endl;
              return *this;
          }
      };
      
      int main() {
          cout << "starting test 1" << endl;
          {
              Foo f = Foo();
          }
          cout << "ending test 1" << endl << endl;
          cout << "starting test 2" << endl;
          {
              Foo f = Foo().bar();
          }
          cout << "ending test 2" << endl;
          return 0;
      }
      

      --

      starting test 1
      -- constructor 000000EC09CFF944
      -- destructor  000000EC09CFF944
      ending test 1
      
      starting test 2
      -- constructor 000000EC09CFFA44
      ---- bar call  000000EC09CFFA44
      -- copy-constructor 000000EC09CFF964
      -- destructor  000000EC09CFFA44
      -- destructor  000000EC09CFF964
      ending test 2
      

      【讨论】:

        【解决方案4】:

        这是预期的行为。即使您从bar 返回引用,您也没有使用它。

        Foo f = Foo().bar();
        

        按值捕获返回值,以便您制作副本。这意味着来自Foo()Foo 在表达式的末尾被销毁,而您在f 中的副本在您退出范围时被销毁。

        【讨论】:

          猜你喜欢
          • 2016-10-15
          • 2015-02-25
          • 2012-11-21
          • 1970-01-01
          • 1970-01-01
          • 2013-12-11
          • 2021-11-06
          相关资源
          最近更新 更多