C ++:std :: move with rvalue reference不是移动内容

时间:2015-07-03 20:01:21

标签: c++ c++11 move move-semantics

示例程序:

#include <iostream>
#include <string>
#include <vector>

template <typename T>
void print(const T& _vec)
    {        
        for( auto c: _vec )
            std::cout << c << ",";
    }

typedef std::vector<std::string> vecstr_t;

struct Trade
{
    explicit Trade(vecstr_t&& vec) : _vec(vec )
    {       
    }  

     vecstr_t _vec;
};


int main()
{   
    vecstr_t tmpV = {"ONE", "TWO", "THREE", "FOUR"};    
    std::cout << "size 1:" << tmpV.size() << "\t"; print(tmpV); std::cout <<  "\n" ;    
    Trade t(std::move(tmpV));    
    std::cout << "size 2:" << tmpV.size() << "\t";  print(tmpV); std::cout <<  "\n" ; // expted tmpV should be e,pty but it has original contents    
    print(t._vec);    
}

我希望尺寸2:应该是ZERO,但输出是:

size 1:4    ONE,TWO,THREE,FOUR,
size 2:4    ONE,TWO,THREE,FOUR,
ONE,TWO,THREE,FOUR,

1 个答案:

答案 0 :(得分:6)

explicit Trade(vecstr_t&& vec) : _vec(vec)
{}

在上面的构造函数中,即使vec的类型为vecstr_t 的rvalue引用,它本身也是一个左值。要记住的基本规则是 - if it has a name, it's an lvalue

很少有上下文可以自动移动左值(例如按值返回对象的函数的return语句),但构造函数的 mem-initializer 列表不是其中之一。

在您的示例中,_vec是从vec构建的副本。如果您希望将其移动构造,请使用std::move

explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}

现在对print的第二次调用不会打印任何内容。请注意,从技术上讲,第二个调用可以打印非零大小,因为未指定从vector移动的内容。但是在大多数(可能是所有)实现中,您会看到一个空的vector

Live demo

您在下面的评论中表示您的意图是接受rvalues和左值,仅在前者的情况下移动,否则复制参数。按照目前的编写,你的构造函数只接受rvalues,而不是lvalues。有几种不同的选择可以达到你想要的效果。

最简单的方法是更改​​参数,使其按值获取参数,然后无条件地移动。

explicit Trade(vecstr_t vec) : _vec(std::move(vec))
{}

这种方法的缺点是你可能会招致vector的额外移动构造,但构建vector的移动很便宜,在大多数情况下你应该选择这个选项。

第二个选项是创建构造函数的两个重载

explicit Trade(vecstr_t&&      vec) : _vec(std::move(vec)) {}
explicit Trade(vecstr_t const& vec) : _vec(vec)            {}

这个缺点是随着构造函数参数的数量增加,重载次数将呈指数级增长。

第三种选择是使用完美转发。

template<typename V>
explicit Trade(V&& vec) : _vec(std::forward<V>(vec)) {}

上面的代码将保留传递给构造函数的参数的值类别,当它转发给构造函数_vec时。这意味着如果vec是右值,则将调用vecstr_t移动构造函数。如果它是一个左值,它将被复制。

此解决方案的缺点是您的构造函数将接受任何类型的参数,而不仅仅是vecstr_t,然后如果参数不可转换为mem-initializer列表中的move / copy构造将失败vecstr_t。这可能导致错误消息,使用户感到困惑。