【问题标题】:Destuctor called twice when returning a temporary objects with default constructor使用默认构造函数返回临时对象时调用两次析构函数
【发布时间】:2014-11-29 08:21:45
【问题描述】:

问题

析构函数在以下代码中被调用了两次:

class Foo
{
public: 
    ~Foo()
    {
        std::cout << "Destructor called\n";
    }

    Foo& operator=(const Foo& other)
    {
        std::cout << "Assignment called\n";

        return *this;
    }
};

Foo foo()
{
    return Foo();
}

int main()
{
    foo();

    return 0;
}

输出:

Destructor called
Destructor called

我怀疑这是由于对赋值运算符或复制构造函数的一些隐式调用。我不知道是否调用了复制构造函数,因为添加任何类型的构造函数都会神奇地解决问题(如下所述),但至少不会调用赋值运算符。

如前所述,如果我添加一个构造函数,问题就会消失:

class Foo
{
public:
    Foo()
    {
        std::cout << "Constructor called\n";
    }

    Foo(const Foo& other)
    {
        std::cout << "Copy constructor called\n";
    }

    ~Foo()
    {
        std::cout << "Destructor called\n";
    }

    Foo& operator=(const Foo& other)
    {
        std::cout << "Assignment called\n";

        return *this;
    }
};

输出变为:

Constructor called
Destructor called

如果我返回引用而不是对象,问题也会消失(但会导致“本地或临时变量的返回地址”警告):

Foo& foo()
{
    return Foo();
}

问题

为什么析构函数被调用两次,为什么在使用默认构造函数时行为不同?是否有合乎逻辑的解释,或者编译器是否有错误?

如果有什么不同的话,我正在使用 MSVC 2013。

【问题讨论】:

  • 您忘记包含复制构造函数。并在Release构建中测试代码,以便返回值优化可以避免额外的复制。
  • 赋值运算符应该返回非常量引用。
  • 析构函数调用次数取决于构造函数调用次数,这取决于优化,即编译器和选项。
  • 你怎么知道第一个例子没有调用拷贝构造函数?

标签: c++ constructor language-lawyer destructor


【解决方案1】:

return 语句将您的返回值复制到调用函数范围内的临时对象中,即 main。然后在 foo 范围内创建的临时对象 Foo() 被破坏,然后在 main 范围内的临时对象被破坏。编译器被允许但没有义务对其进行优化。

【讨论】:

    【解决方案2】:

    在函数foo() 中,您的对象被创建。它返回该对象的副本。当函数结束时,第一个对象(在函数中创建)超出范围,调用析构函数。

    接下来,你 main() 得到一个全新的对象。当它结束时,它会调用这个新对象的析构函数。

    对于以下:

    Foo& foo()
    {
       return Foo();
    }
    

    ..你返回一个局部变量的引用,它给出了一个毫无价值的返回,主函数根本没有得到一个对象,没有重复的析构函数被调用。

    【讨论】:

    • 您正在返回对局部变量的引用。
    • 这段代码甚至不应该编译,你不能将prvalue绑定到左值引用。
    • 是的。感谢您指出。我不知道我在想什么哈哈
    【解决方案3】:
    Foo foo()
    {
        return Foo(); // temporary is created and copied into the return value. 
                      // The temporary gets destroyed at the end of the copy constructor.
    }
    
    int main()
    {
        foo(); // discarded-value expression; the return value gets destroyed immediately.
    }
    

    允许编译器应用复制省略 - 在这种情况下,NRVO(第 12.8/31 节):

    ——当一个尚未绑定到引用 (12.2) 的临时类对象将被复制/移动时 对于具有相同 cv-unqualified 类型的类对象,可以通过以下方式省略复制/移动操作 将临时对象直接构造到省略的复制/移动的目标中

    请注意,无论复制构造函数是否有任何副作用,这都有效。这是一种“优化”,可以在不违反标准的情况下合法地改变程序的可观察行为。

    由于编译器没有义务优化任何东西,结果可能因代码(和优化级别)而异。

    【讨论】:

    • 我最喜欢你的回答。但我还想指出一个重要的事实,即即使复制/移动对象有副作用stackoverflow.com/questions/12953127/…(信任@P0W),也可以应用复制省略。
    • @Adelost 我知道,我认为通过引用很明显。我应该在答案中指出吗?
    • 我想是的。这让我很困惑,因为我不认为当优化有副作用时允许编译器进行优化,有时最好是明确的。
    猜你喜欢
    • 2015-12-14
    • 1970-01-01
    • 2016-10-10
    • 2015-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-27
    • 2018-05-13
    相关资源
    最近更新 更多