为什么 C++ 复制构造函数被调用两次?

Why is C++ copy constructor called twice?

我有一些代码 return 是一个 class 对象,并且复制构造函数被调用的次数比我想象的要多。这似乎是 g++ 编译器的工作方式,但我不确定为什么。假设我有这个代码:

#include <memory>
#include <iostream>

using namespace std;

class A
{
public:
    A() { cout << "constructor" << endl; }
    A(const A& other) { cout << "copy constructor" << endl; }
    ~A() { cout << "destructor" << endl; }
};

A f()
{
    return A();
}

int main()
{
    cout << "before f()\n";
    A a = f();
    cout << "after f()\n";
    return 0;
}

在关闭构造函数省略的情况下编译

g++ test.cpp -fno-elide-constructors

它输出这个:

before f()
constructor
copy constructor
destructor
copy constructor
destructor
after f()
destructor

所以复制构造函数被调用了两次。我明白为什么在将 f() 的 return 值复制到变量 a 时会调用复制构造函数,但为什么又要调用?

我读过有关 C++ 编译器 return 从函数中获取临时对象的信息,但我不明白其中的意义。即使关闭省略,似乎也没有必要为每个函数 return 值创建一个临时对象。

更新

在这里要说得非常清楚,我对调用复制构造函数并不感到惊讶。我预计,因为我关闭了省略。我感到困惑的是为什么复制构造函数需要被调用两次。似乎一次就足以将结果从 f() 复制到 a.

这是因为在 C++17 之前,编译器没有强制删除副本。如果您 do 打开编译为 C++17 或更高版本,将只有一个构造函数调用 - 即使您使用 -fno-elide-constructors.

Demo

C++14中-fno-elide-constructors的实例是这样创建的:

A // 2:nd (1:st copy)
f()
{
    return A();
    //     1:st
}

    A a = f();
//  3:rd (2:nd copy)

如果改为 return {};,则可以跳过第一个临时实例,只获取一个副本:

A // 1:st
f()
{
    return {};
    //    arg to ctor
}

    A a = f();
//  2:nd (1:st copy)

Demo

Pre-C++17

在 Pre-C++17 标准中有 non-mandatory 复制 elison,所以通过使用 -fno-elide-constructors 标志你是 禁用 return 值优化和其他一些省略副本的优化。

这就是您的程序中发生的情况:

  1. 首先由于 return 语句 return A(); 使用 默认构造函数 A::A().
  2. 构造了一个对象
  3. 接下来,该默认构造对象的副本被 returned 给调用者。这是使用复制构造函数 A::A(const A&) 完成的。因此,您将获得第一个复制构造函数调用输出。
  4. 最后,由于 A a = f(); 语句 复制初始化 ,名为 a 的对象被创建为 return 的副本]ed 使用复制构造函数 A::A(const A&) 的值。因此你得到了第二个复制构造函数调用输出。
//--v----------------------->1st copy constructor called due to this(return by value) 
    A f()
    {
//-------------vvv---------->default constructor called due to this
        return A();
    }
//--vvvvvvvvv--------------->2nd copy constructor called due to this(copy initialization)
    A a = f();

C++17

在 C++17(及以后)中,由于 mandatory copy elison, you will not get any copy constructor call output. Demo