使用默认构造函数返回临时对象时,析构函数调用两次

时间:2014-10-04 13:04:04

标签: c++ constructor language-lawyer destructor

问题

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

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,如果它可以有所作为。

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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.
}

允许编译器应用copy-elision - 在这种情况下,NRVO(§12.8/ 31):

  

- 当复制/移动尚未绑定到引用(12.2)的临时类对象时       对于具有相同cv-unqualified类型的类对象,可以省略复制/移动操作       将临时对象直接构造到省略的复制/移动

的目标中

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

由于编译器没有义务优化任何内容,因此代码与代码(以及优化级别)的结果可能不同。

答案 2 :(得分:-1)

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

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

对于以下内容:

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

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