为什么在使用 std::make_pair 从函数返回一对时,结构化绑定不会发生 RVO?



考虑这段代码,它定义了一个简单的结构体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_*帮助程序。当您可以使用构造函数的参数调用它们时,最好使用它们,以便它们原地构造对象。

相关内容

  • 没有找到相关文章

最新更新