【问题标题】:arguments to tuple are copied instead of moved when returning the tuple from a function从函数返回元组时,元组的参数被复制而不是移动
【发布时间】:2018-01-23 11:24:24
【问题描述】:

我对以下代码有疑问。我的编译器是 MSVC++ 17 Visual Studio 版本 15.3,编译器选项 /std:c++14(相对于 /std:c++latest)在发布模式下运行:

struct Bar
{
    int a;
    std::string b;
    Bar() { std::cout << "default\n";  }
    Bar(int a, const std::string& b)    : a{ a }, b{ b } { std::cout << "direct\n"; }
    Bar(int a, std::string&& b)         : a{ a }, b{ std::move(b) } { std::cout << "direct move b\n"; }
    Bar(const Bar& other)               : a{ other.a }, b{ other.b } { std::cout << "const copy\n"; }
    Bar(Bar&& other)                    : a{ std::move(other.a) }, b{ std::move(other.b) } { std::cout << "move\n"; }
    Bar& operator=(const Bar& other)
    {
        a = other.a;
        b = other.b;
        std::cout << "const assign\n";
        return *this;
    }

    Bar& operator=(Bar&& other)
    {
        a = std::move(other.a); //would this even be correct?
        b = std::move(other.b); 
        std::cout << "move assign\n";
        return *this;
    }
};

std::tuple<Bar, Bar> foo()
{
    std::string s = "dsdf";
    return { { 1, s }, { 5, "asdf" } };
}

int main()
{
    Bar a, b;
    std::tie(a, b) = foo();
    std::cout << a.a << a.b << std::endl;
    std::cout << b.a << b.b;
}

输出是:

default
default
direct
direct move b
const copy <-- Why copy? Why not move>
const copy <-- Why copy? Why not move>
move assign
move assign
1dsdf
5asdf

如果我将return { { 1, s }, { 5, "asdf" } }; 更改为return { Bar{ 1, s }, Bar{ 5, "asdf" } };,输出将更改为:

default
default
direct
direct move b
move
move
move assign
move assign
1dsdf
5asdf

问题:为什么在这两种情况下都不执行移动?为什么第一种情况会调用拷贝构造函数?

【问题讨论】:

    标签: c++ visual-c++ c++14 visual-studio-2017 move-semantics


    【解决方案1】:

    你的问题最简单的提炼是为什么:

    std::tuple<Bar> t{{5, "asdf"}};
    

    打印

    direct move b
    const copy
    

    但是

    std::tuple<Bar> u{Bar{5, "asdf"}};
    

    打印

    direct move b
    move
    

    要回答这个问题,我们必须确定这两个声明的实际作用。为了做到这一点,我们必须了解std::tuple's constructors 中的哪一个被调用。相关的是(每个构造函数的explicitness 和constexprness 都不相关,因此为简洁起见,我将它们省略):

    tuple( const Types&... args ); // (2)
    
    template< class... UTypes >
    tuple( UTypes&&... args );     // (3)
    

    使用Bar{5, "asdf"} 初始化将调用构造函数(3) 作为更好的匹配((2)(3) 都是可行的,但我们在(3) 中获得的 cv 限定引用较少),这将从UTypes 进入 tuple。这就是我们最终选择move 的原因。

    但是只使用{5, "asdf"} 进行初始化,这个构造函数是不可行的,因为braced-init-lists 没有可以推断的类型。因此我们的 only 选项是(2),我们最终得到一个副本。

    解决此问题的唯一方法是添加对每个Types 进行右值引用的非模板构造函数。但是您将需要2^N-1 这样的构造函数(除了接受所有 const 左值引用的构造函数之外的所有构造函数——因为可以推导出一个),因此我们最终得到了一种适用于所有情况但次优的设计。但是由于您可以在调用站点上指定所需的类型,因此这不是一个大缺陷。

    【讨论】:

      猜你喜欢
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      • 2013-05-16
      • 2018-06-21
      • 2022-11-29
      • 2011-07-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多