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

Tel*_*ope 17 c++ return-value-optimization copy-elision c++17

考虑这段代码,它定义了一个简单的结构Test(带有默认构造函数和复制构造函数)并std::pair <Test, Test>从函数返回 a 。

#include <iostream>
#include <utility>

using namespace std;

struct Test {
    Test() {}
    Test(const Test &other) {cout << "Test copy constructor called\n";}
};

auto func() {
    return make_pair(Test(), Test());
}

int main()
{
    auto [a, b] = func(); // Expectation: copies for a and b are both elided

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,输出是

Test copy constructor called
Test copy constructor called
Run Code Online (Sandbox Code Playgroud)

而修改func为

Test copy constructor called
Test copy constructor called
Run Code Online (Sandbox Code Playgroud)

导致复制构造函数未被调用。我的 g++ 版本是 11.2.0,所以我认为在这种情况下可以保证复制省略,但我可能是错的。有人可以确认我是否误解了 RVO 吗?

use*_*522 19

std::make_pair是一个通过引用获取参数的函数。因此,临时变量是从两个Test()参数创建的,并从这些参数std::make_pair构造 a std::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());
}
Run Code Online (Sandbox Code Playgroud)


Nic*_*las 8

make_pair不是一个类型;这是一个函数。函数带有参数。这些参数必须是对象或对象的引用。因此,您作为参数传递的纯右值将显示临时值,然后将其用于初始化从make_pair.

由于您的类型是仅复制的,因此它将通过您类型的复制构造函数来执行此操作。


Gos*_*low 5

这与 RVO 无关。你打电话时

return make_pair(Test(), Test());
Run Code Online (Sandbox Code Playgroud)

返回值是一对,该对是返回值优化的,因此没有复制该对。

问题是您make_pair使用 2 个类型的对象调用该函数Test。要生成该对,必须将 2 个参数复制到结果对中。所以你缺少的是 AVO,参数值优化,这是 C++ 所没有的。

您可以通过分段构造一对来避免这种情况:

auto func() {
    return pair<Test, Test>{std::piecewise_construct, tuple(), tuple()};
}
Run Code Online (Sandbox Code Playgroud)

在此语法中,您传递构造函数的参数而不是已构造的对象。这样就pair可以将两个Test对象构建到 RVO 优化位置,而无需复制。

缺点是现在您必须担心复制构造函数参数。

如果您不希望复制或移动已创建的对象,通常不要调用任何 STLemplace_*或助手。make_*当您可以使用构造函数的参数调用它们时,最好使用它们,以便它们就地构造对象。