Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/129.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 使用默认构造函数返回临时对象时,destructor调用了两次_C++_Constructor_Language Lawyer_Destructor - Fatal编程技术网

C++ 使用默认构造函数返回临时对象时,destructor调用了两次

C++ 使用默认构造函数返回临时对象时,destructor调用了两次,c++,constructor,language-lawyer,destructor,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; } }; Fo

问题

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

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;
}
我怀疑这是由于对赋值运算符或复制构造函数的隐式调用造成的。我无法判断复制构造函数是否会被调用,因为添加任何类型的构造函数都能神奇地解决问题(如下面进一步解释的),但至少赋值运算符不会被调用

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

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;
    }
};
如果我返回一个引用而不是一个对象,问题也会消失(但会导致“返回本地或临时变量的地址”警告):

问题

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


如果MSVC 2013有什么不同,我就使用它。

return语句将返回值复制到调用函数范围内的临时对象中,即main。然后在Foo的作用域中创建的临时对象Foo()被销毁,然后在main的作用域中创建的临时对象被销毁。编译器可以但没有义务对此进行优化。

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

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

有关下列事项:

Foo& foo()
{
   return Foo();
}
..您返回对局部变量的引用,该局部变量给出了一个毫无价值的返回,并且main函数根本不获取对象,因此不会调用重复的析构函数

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类型的类对象,可以通过 将临时对象直接构造到省略的复制/移动的目标中

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


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

可能重复您忘记包含复制构造函数的内容。并在发布版本中测试代码,以便返回值优化可以避免额外的副本。赋值运算符应返回非常量引用。析构函数调用的数量取决于构造函数调用的数量,而构造函数调用的数量取决于优化,例如编译器和选项。您如何知道在第一个示例中没有调用复制构造函数?您正在返回对局部变量的引用。此代码甚至不应该编译,您不能将prvalue绑定到左值引用。是的。谢谢你指出。我不知道我在想什么lolI最喜欢你的答案。但我还想指出一个重要事实,即即使复制/移动对象有副作用(cred to@P0W),也可以应用复制省略。我知道,我认为这一点通过引用是显而易见的。我应该在回答中指出吗?我想是的。这让我很困惑,因为我不认为编译器可以在优化有副作用时进行优化,而且有时候最好是显式的。
Foo& foo()
{
    return Foo();
}
Foo& foo()
{
   return Foo();
}
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.
}