为什么 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
.
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)
Pre-C++17
在 Pre-C++17 标准中有 non-mandatory 复制 elison,所以通过使用 -fno-elide-constructors
标志你是 禁用 return 值优化和其他一些省略副本的优化。
这就是您的程序中发生的情况:
- 首先由于 return 语句
return A();
使用 默认构造函数 A::A()
. 构造了一个对象
- 接下来,该默认构造对象的副本被 returned 给调用者。这是使用复制构造函数
A::A(const A&)
完成的。因此,您将获得第一个复制构造函数调用输出。
- 最后,由于
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。
我有一些代码 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
.
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)
Pre-C++17
在 Pre-C++17 标准中有 non-mandatory 复制 elison,所以通过使用 -fno-elide-constructors
标志你是 禁用 return 值优化和其他一些省略副本的优化。
这就是您的程序中发生的情况:
- 首先由于 return 语句
return A();
使用 默认构造函数A::A()
. 构造了一个对象
- 接下来,该默认构造对象的副本被 returned 给调用者。这是使用复制构造函数
A::A(const A&)
完成的。因此,您将获得第一个复制构造函数调用输出。 - 最后,由于
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。