示例程序:
#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,
答案 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
。
您在下面的评论中表示您的意图是接受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
。这可能导致错误消息,使用户感到困惑。