2017-08-15 40 views
4

我对下面的代码有疑问。我的编译器是MSVC++ 17的Visual Studio版本15.3与编译器选项/ STD:C++ 14(与/ STD:C++最新)在释放模式运行:从元组返回元组时复制元组的参数被复制而不是移动

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 

问题:为什么在这两种情况下都不行?为什么在第一种情况下调用复制构造函数?

回答

2

你的问题的最简单的蒸馏就是:

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被调用。相关的有(既不是每个构造的explicit内斯和constexpr内斯是相关的,所以我忽略他们为了简洁):

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-list s没有可推断的类型。因此我们的只有选项是(2),我们最终得到一份副本。

解决这个问题的唯一方法是添加非模板构造函数,将右值引用给每个Types。但是你需要这样的构造函数(除了那些接受所有常量左值引用的构造函数外 - 因为可以推导出这个值),所以我们最终得到的设计可以适用于所有情况,但并不理想。但既然你可以在呼叫站点上指定你想要的类型,这不是一个巨大的缺陷。