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
类型,它本身也是一个左值。要记住的基本规则是 - if it has a name, it's an lvalue.
很少有上下文可以自动移动左值(例如函数的 return 语句 return 按值创建对象),但是构造函数的 mem-initializer 列表不是其中之一。
在您的示例中,_vec
是从 vec
构建的副本。如果您希望它改为移动构造,请使用 std::move
.
explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}
现在第二次调用 print
将不会打印任何内容。请注意,从技术上讲,第二次调用可以打印非零大小,因为未指定从 vector
移动的内容。但是在大多数(可能是所有)实现中,你会看到一个空的 vector
.
你在下面的评论说你的意图是接受右值和左值,只在前者的情况下移动,否则复制参数。正如目前所写,您的构造函数将只接受右值,而不接受左值。有几个不同的选项可以实现您想要的。
最简单的可能是更改参数,使其按值获取参数,然后无条件移动。
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
,然后内存初始化器列表中的 move/copy 构造将失败,如果参数不能转换为 vecstr_t
。这可能会导致使用户感到困惑的错误消息。
示例程序:
#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
类型,它本身也是一个左值。要记住的基本规则是 - if it has a name, it's an lvalue.
很少有上下文可以自动移动左值(例如函数的 return 语句 return 按值创建对象),但是构造函数的 mem-initializer 列表不是其中之一。
在您的示例中,_vec
是从 vec
构建的副本。如果您希望它改为移动构造,请使用 std::move
.
explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}
现在第二次调用 print
将不会打印任何内容。请注意,从技术上讲,第二次调用可以打印非零大小,因为未指定从 vector
移动的内容。但是在大多数(可能是所有)实现中,你会看到一个空的 vector
.
你在下面的评论说你的意图是接受右值和左值,只在前者的情况下移动,否则复制参数。正如目前所写,您的构造函数将只接受右值,而不接受左值。有几个不同的选项可以实现您想要的。
最简单的可能是更改参数,使其按值获取参数,然后无条件移动。
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
,然后内存初始化器列表中的 move/copy 构造将失败,如果参数不能转换为 vecstr_t
。这可能会导致使用户感到困惑的错误消息。