简体   繁体   English

为什么我不能通过 std::tuple 获得保证的复制省略?

[英]Why do I not get guaranteed copy elision with std::tuple?

I would expect that in C++20 the following code prints nothing between prints of A and B (since I expect guaranteed RVO to kick in).我希望在 C++20 中,以下代码在 A 和 B 的打印之间不打印任何内容(因为我希望保证 RVO 启动)。 But output is:但输出是:

A一种

Bye再见

B

C C

Bye再见

Bye再见

So presumably one temporary is being created.因此,大概正在创建一个临时对象。

#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";
}

What is the reason for this behavior?这种行为的原因是什么? Is there a workaround to avoid copy (without using pointers)?是否有避免复制的解决方法(不使用指针)?

https://godbolt.org/z/zasoGd https://godbolt.org/z/zasoGd

When constructing std::tuple<int, INeedElision> from {i, {47}} , the selected constructor of std::tuple takes elements by lvalue-reference to const .当从{i, {47}}构造std::tuple<int, INeedElision> std::tuple的选定构造函数通过对const左值引用获取元素。

 tuple( const Types&... args );

Then when use {i, {47}} as the initializer, a temporary INeedElision will be constructed and then passed to the constructor of std::tuple (and get copied).然后当使用{i, {47}}作为初始化器时,将构造一个临时的INeedElision然后传递给std::tuple的构造函数(并被复制)。 The temporary object will be destroyed immediately and you'll see "Bye" between "A" and "B".临时对象将立即销毁,您将在“A”和“B”之间看到“Bye”。

BTW: The 3rd constructor of std::tuple won't be used for this case.顺便说一句: std::tuple的第三个构造函数不会用于这种情况。

 template< class... UTypes > tuple( UTypes&&... args );

It's a constructor template, and braced-init-list like {47} doesn't have type and can't be deduced by template argument deduction.它是一个构造函数模板,像{47}这样的花括号初始化列表没有类型并且不能通过模板参数推导来推导。

On the other hand, if INeedElision has a converting constructor taking int , and make the initializer as {i, 47} , the 3rd constructor of std::tuple will be used and no temporary INeedElision is constructed;另一方面,如果INeedElision有一个采用int的转换构造函数,并将初始值设定项设为{i, 47} ,则将使用std::tuple的第三个构造函数并且不构造临时INeedElision the element will be constructed in-place from the int 47 .该元素将从int 47就地构造。

LIVE居住

you only get copy elision if you return the object itself :如果您返回对象本身,您只会得到复制省略:

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
}

in your case you are returning tuple so the tuple itself maybe copy elided but not the arguments passed to the constructor of the tuple !在您的情况下,您正在返回元组,因此元组本身可能会被复制,但不会复制传递给元组构造函数的参数!

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
}

the compiler isn't allowed to elide the copy of arguments passed to the tuple constructor because you aren't returning the arguments themselves but rather the tuple containing copy of them .不允许编译器省略传递给元组构造函数的参数副本,因为您返回的不是参数本身,而是包含它们的副本的元组。 Also note that the table can't hold references to the arguments because these local variables will have been destructed by the time the function returns resulting in a dangling references .另请注意,该表不能保存对参数的引用,因为这些局部变量将在函数返回时被破坏,从而导致悬空引用。

if you want to get a chance for copy elision in c++ 17 and later do something like this :如果你想有机会在 c++ 17 和更高版本中复制省略,请执行以下操作:

std::tuple<int, INeedElision> f(){

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM