【问题标题】:Why do I not get guaranteed copy elision with std::tuple?为什么我不能用 std::tuple 保证复制省略?
【发布时间】:2020-12-13 00:48:04
【问题描述】:

我希望在 C++20 中,以下代码在 A 和 B 的打印之间不打印任何内容(因为我希望保证 RVO 能够启动)。但输出是:

一个

再见

B

C

再见

再见

所以大概正在创建一个临时的。

#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

【问题讨论】:

    标签: c++ c++20 stdtuple copy-elision rvo


    【解决方案1】:

    当从{i, {47}} 构造std::tuple&lt;int, INeedElision&gt; 时,the selected constructor of std::tuple 通过对const 的左值引用获取元素。

    tuple( const Types&... args );
    

    然后当使用{i, {47}}作为初始化器时,会构造一个临时的INeedElision,然后传递给std::tuple的构造函数(并被复制)。临时对象将立即销毁,您会在“A”和“B”之间看到“Bye”。

    顺便说一句:std::tuple 的第三个构造函数不会用于这种情况。

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

    它是一个构造函数模板,像{47}这样的braced-init-list没有类型,不能通过模板参数推导推导出来。

    另一方面,如果INeedElision有一个转换构造函数取int,并将初始化器设为{i, 47},则将使用std::tuple的第三个构造函数,不会构造临时的INeedElision;该元素将从int 47 就地构造。

    LIVE

    【讨论】:

    • 这不是std::tuple 的唯一构造函数。它实际上是在这种情况下使用的那个吗?
    • @MarkusMayr​​ 我想是的。你期待哪一个?
    • @songyuanyao 可能和我一样,有些版本采用 && args 和 std::forward 在构造函数中分配它们
    • @NoSenseEtAl 你是说第三个?不,它是一个构造函数模板,像 {47} 这样的花括号初始化列表不适用于模板参数推导。
    • @NoSenseEtAl 你可能想要this
    【解决方案2】:

    如果你返回对象本身,你只会得到复制省略:

    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 不保证 AFAIK
    • @NoSenseEtAl 是的,我确实这么说
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-08
    • 1970-01-01
    • 2021-08-21
    • 2016-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多