C ++:带有右值引用的std :: move不移动内容 [英] C++: std::move with rvalue reference is not moving contents

查看:147
本文介绍了C ++:带有右值引用的std :: move不移动内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

示例程序:

#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:应该为零,但输出为:

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

解决方案

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

在上面的构造函数中,即使vecvecstr_t 的右值引用类型,它本身也是一个左值.要记住的基本规则是-如果有名称,则是左值. >

在极少数情况下可以自动移去左值(例如,按值返回对象的函数的return语句),但是构造函数的 mem-initializer 列表不是一个其中.

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

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

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

实时演示


您在下面的评论中说,您的意图是接受右值和左值,仅在前者的情况下移动,否则复制参数.如当前所写,您的构造函数将仅接受rvalues,而不接受lvalues.有几种不同的选项可以实现您想要的.

最简单的方法可能是更改参数,以使其按值获取参数,然后无条件移动.

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

这种方法的缺点是您可能会产生vector的其他move构造,但是构造vector的move构造很便宜,并且在大多数情况下,您应该使用此选项.

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

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 move构造函数.并且如果是左值,则会从中复制.

此解决方案的缺点是构造函数将接受任何类型的参数,而不仅仅是vecstr_t,如果参数不能转换为.这可能会导致错误消息,使用户感到困惑.

Sample program:

#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);    
}

I expect size 2: should be ZERO but output is:

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

解决方案

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

In the constructor above, even though vec is of type rvalue reference to vecstr_t, it is itself an lvalue. The basic rule to remember is - if it has a name, it's an lvalue.

There are very few contexts where an lvalue may automatically be moved from (such as the return statement of a function that returns an object by value), but a constructor's mem-initializer list is not one of them.

In your example, _vec is copy constructed from vec. If you want it to be move constructed instead, use std::move.

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

Now the second call to print will not print anything. Note that technically the second call could print a non-zero size because the contents of a moved from vector are unspecified. But on most (probably all) implementations, you'll see an empty vector.

Live demo


Your comment below says your intent is to accept both rvalues and lvalues, move only in the case of the former, and copy the argument otherwise. As currently written, your constructor will only accept rvalues, and not lvalues. There are a few different options to achieve what you want.

The easiest probably is to change the parameter so that it's taking the argument by value, and then unconditionally move.

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

The drawback with this approach is that you may incur an additional move construction of the vector, but move constructing a vector is cheap, and you should go with this option in most cases.

The second option is to create two overloads of the constructor

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

The drawback with this one is that the number of overloads will increase exponentially as the number of constructor arguments increases.

The third option is to use perfect forwarding.

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

The code above will preserve the value category of the argument passed to the constructor when it forwards it to construct _vec. This means that if vec is an rvalue, the vecstr_t move constructor will be called. And if it is an lvalue, it will be copied from.

The drawback with this solution is that your constructor will accept any type of argument, not just a vecstr_t, and then the move/copy construction in the mem-initializer list will fail if the argument is not convertible to vecstr_t. This may result in error messages that are confusing to the user.

这篇关于C ++:带有右值引用的std :: move不移动内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆