考虑这段代码,它定义了一个简单的结构体Test
(带有默认构造函数和复制构造函数),并从函数返回一个std::pair <Test, Test>
。
#include <iostream>
#include <utility>
using namespace std;
struct Test {
Test() {}
Test(const Test &other) {cout << "Test copy constructor calledn";}
};
auto func() {
return make_pair(Test(), Test());
}
int main()
{
auto [a, b] = func(); // Expectation: copies for a and b are both elided
return 0;
}
令人惊讶的是,输出是
Test copy constructor called
Test copy constructor called
而将func
修改为
auto func() {
return Test();
}
int main()
{
Test a(func());
return 0;
}
导致没有调用复制构造函数。我的g++版本是11.2.0,所以我认为在这种情况下可以保证省略复制,但我可能错了。有人能确认一下我是否误解了RVO吗?
std::make_pair
是一个通过引用获取参数的函数。因此,从两个Test()
参数创建临时变量,std::make_pair
从它们构造一个std::pair
,这需要从参数复制构造pair元素。(移动构造是不可能的,因为手动定义的复制构造函数禁止隐式移动构造函数。)
这与结构化绑定、RVO或std::make_pair
之外的任何东西无关。
因为std::pair
不是一个聚合类,所以也不能通过直接从这两个参数构造std::pair
来解决这个问题。为了让std::pair
从参数列表中构造元素,您需要使用它的std::piecewise_construct
重载:
auto func() {
return std::pair<Test, Test>(std::piecewise_construct, std::forward_as_tuple(), std::forward_as_tuple());
}
make_pair
不是一个类型;它是一个函数。函数接受参数。这些参数必须是对象或对象引用。因此,作为参数传递的右值将显示临时值,这些临时值随后用于初始化从make_pair
返回的右值。
由于你的类型是只复制的,它将通过你的类型的复制构造函数来实现。
这与RVO无关。当你调用
return make_pair(Test(), Test());
返回值是一对,对返回值进行了优化,因此没有复制pair。
问题是你用2个类型为Test
的对象调用函数make_pair
。要创建一对,必须将这两个参数复制到结果对中。所以你缺少的是AVO,参数值优化,这是c++所没有的。
可以通过分段构造一对来避免这种情况:
auto func() {
return pair<Test, Test>{std::piecewise_construct, tuple(), tuple()};
}
在此语法中,传递构造函数的参数而不是已经构造的对象。这样,pair
就可以将两个Test
对象构造到RVO优化的位置,而不需要复制。
缺点是现在你必须担心复制构造函数参数。
如果你不想让它们复制或移动,一般不要对已经创建的对象调用任何STLemplace_*
或make_*
帮助程序。当您可以使用构造函数的参数调用它们时,最好使用它们,以便它们原地构造对象。