为什么使用std::tuple时不能保证复制省略?

11
我预期在C++20中,以下代码在打印A和B之间不会输出任何内容(因为我预期有保证的RVO会发生)。但实际输出为:

A

Bye

B

C

Bye

Bye

因此可以推断出一个临时对象被创建了。

#include <iostream>
#include <tuple>
struct INeedElision{
    int i;
    ~INeedElision(){
        std::cout << "Bye\n";
    }
};

std::tuple<int, INeedElision> f(){
    int i = 47;
    return {i, {47}};
}

INeedElision g(){
    return {};
}

int main()
{   
    std::cout << "A\n"; 
    auto x = f();
    std::cout << "B\n";
    auto y = g();
    std::cout << "C\n";
}
这个行为的原因是什么? 有没有避免复制(不使用指针)的解决方法? https://godbolt.org/z/zasoGd
2个回答

12

{i,{47}}构造std::tuple<int,INeedElision>时,选定的std::tuple构造函数会通过左值引用以const方式接受元素。

tuple( const Types&... args );
当使用{i, {47}}作为初始化器时,将构造一个临时的INeedElision对象并传递给std::tuple的构造函数(并被复制)。这个临时对象会立即被销毁,并且你会在“A”和“B”之间看到“Bye”。
另外,顺便提一下:针对这种情况,std::tuple的第三个构造函数不会被使用。
template< class... UTypes >
tuple( UTypes&&... args );
它是一个构造函数模板,花括号初始化列表如{47}没有类型,并且不能通过模板参数推导来推断。另一方面,如果INeedElision有一个接受int的转换构造函数,并将初始化程序设置为{i,47},则将使用std::tuple的第三个构造函数,并且不会构造任何临时的INeedElision;元素将从int 47中原地构造。

LIVE


1
这不是 std::tuple 的唯一构造函数。实际上在这种情况下使用的是这个构造函数吗? - Markus Mayr
@MarkusMayr 我也这么认为。你期望的是哪一个? - songyuanyao
@songyuanyao会验证您的修复,如果有效则接受它。 - NoSenseEtAl
这个代码可行,但C++实在太糟糕了...添加构造函数以使完美的*后向传递(backwarding)*能够工作 :) - NoSenseEtAl
吹毛求疵:我建议你在回答中加上“已选择的”构造函数...因为初学者可能会认为那是唯一的元组构造函数... - NoSenseEtAl
显示剩余4条评论

1

只有返回对象本身才能获得复制省略:

std::vector<int> fn1()
{
   return std::vector<int>{}; // guaranteed copy elision
}

std::vector<int> fn2()
{
   std::vector<int> vec;
   return vec; // a good compiler will manage to elide the copy/move here
}

在您的情况下,您返回元组,因此元组本身可能会被复制省略,但不会省略传递给元组构造函数的参数!
std::tuple<int, INeedElision> f(){

    int i = 47;
    return {i, {47}}; // construct the tuple in place of the return address but the arguments are copied into the tuple and not even moved ! to move call std::move explicitly
}

编译器不允许省略传递给元组构造函数的参数的副本,因为您返回的不是参数本身,而是包含它们副本的元组。还要注意,表格无法持有对参数的引用,因为这些局部变量在函数返回时已被销毁,导致悬空引用。
如果您想在C++17及更高版本中获得复制省略的机会,请执行以下操作:
std::tuple<int, INeedElision> f(){

    std::tuple<int, INeedElision> ret;
    auto& [i, ne] = ret;
    i = 47;
    ne = 47;
    return ret;
}

据我所知,NRVO并不是有保障的。 - NoSenseEtAl
1
@NoSenseEtAl 是的,我确实说过。 - dev65

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接