没有析构函数的类返回对象的副本,但是当我添加析构函数时,它会返回相同的对象

Class with no destructor returns copy of object but when I add a destructor it returns the same object

提问人:mdf 提问时间:1/13/2022 更新时间:1/13/2022 访问量:96

问:

我正在玩C++中的类,特别是实现一个带有返回对象的静态“create”函数的私有构造函数。

// main.cpp
#include <iostream>

class Foo {
public:
    static Foo create_foo() {    
        Foo foo{};
        std::cout << "Foo::create_foo():: Address of foo: " << &foo << std::endl;

        return foo;
    }
private:
    Foo() { std::cout << "Foo::Foo():: Constructor" << std::endl; }
};

int main() {
    auto foo = Foo::create_foo();
    std::cout << "main():: Address of foo: " << &foo << std::endl;
    
    return 0;
}

运行上面的代码会创建两个不同的对象:一个 in 和一个 in,如输出所示:FooFoo::create_foo()main()

Foo::Foo():: Constructor
Foo::create_foo():: Address of foo: 0x7ffe5d5e60bf
main():: Address of foo: 0x7ffe5d5e60ef

我的第一个倾向是“好吧,它调用了复制构造函数。因此,我添加了以下公共复制构造函数:

Foo(Foo const&) { std::cout << "Foo::Foo(Foo const&):: Copy constructor" << std::endl; }

这就是我的困惑所在,只需添加一个复制构造函数,现在包含相同的对象:Foo::create_foo()main()

Foo::Foo():: Constructor
Foo::create_foo():: Address of foo: 0x7ffe53c9d63f
main():: Address of foo: 0x7ffe53c9d63f

如果我添加一个公共析构函数,也会发生同样的行为:

~Foo() { std::cout << "Foo::~Foo():: Destructor" << std::endl; }

输出:

Foo::Foo():: Constructor
Foo::create_foo():: Address of foo: 0x7ffed84a400f
main():: Address of foo: 0x7ffed84a400f
Foo::~Foo():: Destructor

我只是好奇为什么添加额外的(复制)构造函数或析构函数会改变我的静态函数的行为。可能是一些编译器优化吗?我正在使用和编译没有任何标志()。gcc version 10.2.1 20201125 (Red Hat 10.2.1-9) (GCC)g++ -o main main.cpp

C++ C++11 析构函数 复制构造函数

评论

2赞 Jarod42 1/13/2022
NRVO 可能适用。
1赞 Eljay 1/13/2022
由于该类没有成员变量,因此它可能具有不寻常的优化模式。添加 a,看看这是否会改变行为。并尝试启用和不启用优化。int data[50];
0赞 P. Saladin 1/14/2022
请注意,还有“复制分配”,而不仅仅是复制结构。我认为“复制作业”是您在主线第一行的内容。甚至移动分配。
0赞 P. Saladin 1/14/2022
另请注意,当您实现复制构造函数或析构函数时,编译器将不再为您创建默认的移动构造函数/赋值操作。
0赞 Jarod42 1/15/2022
@P.Saladin:不使用赋值,而是使用复制/移动构造函数(当然,除非省略(自 C++17 以来是强制性的))auto foo = Foo::create_foo();

答:

1赞 user17732522 1/13/2022 #1

编译器允许省略代码中所有潜在的副本/移动。从 C++17 开始,这甚至是强制性的,除了 from 的返回值的移动构造。Foocreate_foofoo

后一种情况称为命名返回值优化,它仍然是一种可选优化,编译器可以选择应用并省略构造函数调用。

您未在启用优化的情况下进行编译。添加标志,NRVO 将应用于您描述的所有情况。-O2

我不知道为什么 GCC 只有在添加特殊成员函数时才会在没有优化标志的情况下应用 NRVO,但同样令人惊讶的是,它完全没有优化标志就应用了 NRVO。

应用 NRVO 后(以及所有其他省略项也应用,无论是否强制),将只有一个对象,由 into in 直接构造。Foocreate_foofoomain