C ++:带有右值引用的std :: move不移动内容 [英] C++: std::move with rvalue reference is not moving contents
问题描述
示例程序:
#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)
{}
在上面的构造函数中,即使vec
是对vecstr_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
.
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屋!