从函数

时间:2017-08-15 14:06:19

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

我对以下代码有疑问。我的编译器是 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

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

1 个答案:

答案 0 :(得分: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 ness和constexpr都不相关,所以为了简洁我省略了它们:

tuple( const Types&... args ); // (2)

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

使用Bar{5, "asdf"}进行初始化会调用构造函数(3)作为更好的匹配((2)(3)都可行,但我们在{{{{}}中获得了较少的cv限定引用1}}),它会从(3)转发到UTypes。这就是为什么我们最终得到tuple

但只使用move初始化,此构造函数不可行,因为 braced-init-list 没有可推断的类型。因此,我们的选项为{5, "asdf"},我们最终会得到一份副本。

解决此问题的唯一方法是添加非模板构造函数,这些构造函数对每个(2)进行rvalue引用。但是你需要Types这样的构造函数(除了可以推导出所有const左值引用的构造函数之外的所有构造函数),所以我们最终得到的设计在所有情况下都有效,但是不是最理想的。但由于您可以在呼叫站点上指定所需的类型,因此这不是一个巨大的缺陷。