在 C++ 中使用移动语义的正确方法是什么?
What is the correct way to use move semantics in C++?
考虑这段代码:
class A {
private:
std::string data;
public:
void set_data(std::string&& data) {
this->data = std::move(data); // line 6
}
};
int main() {
std::string move_me = "Some string";
A a;
a.set_data(std::move(move_me)); // line 13
}
我知道我们需要在第 13 行调用 std::move()
以便它将左值转换为右值引用(听起来正确吗?我是新手)。
但是,在第 6 行,我们需要再次使用 std::move()
吗?我假设不会,因为我们已经传递了一个右值引用并且将调用 std::string
的移动构造函数。对吗?
您在 #6
行和 #13
行都需要它。
Scott Mayers 就此主题发表了 nice post。
最容易接受的方式是
// 1: full flexibility for the caller to decide where the data state comes from
struct X
{
Y data_;
explicit X(const Y& data) : data_(data) { }
explicit X(Y&& data) : data_(std::move(data)) { }
};
// 2: forced copy or move at the call site and zero-copy move into the internal state of the X
struct X
{
Y data_;
explicit X(Y data) : data_(std::move(data)) { }
};
// 3: same as the setter below, but can have quite different forms based on what exactly is required
struct X
{
Y data_;
template <class... Z>
explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
}
setter 最好在 "transparent" 样式中完成,有效地委托给字段的赋值运算符。
template <typename Arg> void setData(Arg&& arg) {
data_ = std::forward<Arg>(arg);
}
我建议用各种 copy/move constructors/operators 编写一个简单的 class 代码,并使用调试打印进行检测,并稍微玩一下 class 来培养直觉如何使用 &&
、std::forward
和 std::move
。无论如何,这就是我过去所做的事情。
However, on line 6, do we need to use std::move()
again?
是。为什么?因为在 set_data
中,data
(参数)是一个左值,因为它 有一个名字 。两个 std::move
都是在 a
中实际将 move_me
移动到 data
所必需的。
如果没有行 6
上的 std::move
,move_me
将不会被移动,因为那样会调用 std::string(const std::string&)
,而不是 std::string(std::string&&)
。
记住 - 如果某物有名字,它是一个左值。
这两个答案似乎都是正确的,我只是添加了标准中的一段,解释了为什么在 #6
行和 #13
行中使用 std::move()
是正确的,以及为什么它是左值,即使类型是行 #6
.
中的右值
The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
5.1.1[expr.prim.general]/8
因此,应用标准中的这条规则,我们有望直接得到答案。
左值
// move_me is identifier of a variable denotes to itself the result is lvalue
std::string move_me = "Some string";
右值
// constructing temporary e.g no identifier is an rvalue
std::string("Some string") ;
左值
// the variable data has type rvalue reference to move_ms, it denotes entity move_ms
// the result is lvalue
void set_data(std::string&& data);
左值
// the variable data has type lvalue reference to move_ms,
//it denotes entity move_ms the result is lvalue
void set_data(std::string& data);
左值或右值 - 通用引用
//the variable data has type universal reference it either holds lvalue or rvalue
template<typename T> void setdata(T && data) ;
所以,右值引用不是右值,事情可能会出错
Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics
如果你错过了使用 std::move()
Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
{
// Derived-specific stuff
}
正确的版本是:
Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
// Derived-specific stuff
}
还有
- 创建对左值的左值引用 - OK
- 创建对右值的右值引用 - OK
- 创建对右值的左值 const 引用 - OK
- 创建对右值的左值引用 - 编译错误
考虑这段代码:
class A {
private:
std::string data;
public:
void set_data(std::string&& data) {
this->data = std::move(data); // line 6
}
};
int main() {
std::string move_me = "Some string";
A a;
a.set_data(std::move(move_me)); // line 13
}
我知道我们需要在第 13 行调用 std::move()
以便它将左值转换为右值引用(听起来正确吗?我是新手)。
但是,在第 6 行,我们需要再次使用 std::move()
吗?我假设不会,因为我们已经传递了一个右值引用并且将调用 std::string
的移动构造函数。对吗?
您在 #6
行和 #13
行都需要它。
Scott Mayers 就此主题发表了 nice post。
最容易接受的方式是
// 1: full flexibility for the caller to decide where the data state comes from
struct X
{
Y data_;
explicit X(const Y& data) : data_(data) { }
explicit X(Y&& data) : data_(std::move(data)) { }
};
// 2: forced copy or move at the call site and zero-copy move into the internal state of the X
struct X
{
Y data_;
explicit X(Y data) : data_(std::move(data)) { }
};
// 3: same as the setter below, but can have quite different forms based on what exactly is required
struct X
{
Y data_;
template <class... Z>
explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
}
setter 最好在 "transparent" 样式中完成,有效地委托给字段的赋值运算符。
template <typename Arg> void setData(Arg&& arg) {
data_ = std::forward<Arg>(arg);
}
我建议用各种 copy/move constructors/operators 编写一个简单的 class 代码,并使用调试打印进行检测,并稍微玩一下 class 来培养直觉如何使用 &&
、std::forward
和 std::move
。无论如何,这就是我过去所做的事情。
However, on line 6, do we need to use
std::move()
again?
是。为什么?因为在 set_data
中,data
(参数)是一个左值,因为它 有一个名字 。两个 std::move
都是在 a
中实际将 move_me
移动到 data
所必需的。
如果没有行 6
上的 std::move
,move_me
将不会被移动,因为那样会调用 std::string(const std::string&)
,而不是 std::string(std::string&&)
。
记住 - 如果某物有名字,它是一个左值。
这两个答案似乎都是正确的,我只是添加了标准中的一段,解释了为什么在 #6
行和 #13
行中使用 std::move()
是正确的,以及为什么它是左值,即使类型是行 #6
.
The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise. 5.1.1[expr.prim.general]/8
因此,应用标准中的这条规则,我们有望直接得到答案。
左值
// move_me is identifier of a variable denotes to itself the result is lvalue
std::string move_me = "Some string";
右值
// constructing temporary e.g no identifier is an rvalue
std::string("Some string") ;
左值
// the variable data has type rvalue reference to move_ms, it denotes entity move_ms
// the result is lvalue
void set_data(std::string&& data);
左值
// the variable data has type lvalue reference to move_ms,
//it denotes entity move_ms the result is lvalue
void set_data(std::string& data);
左值或右值 - 通用引用
//the variable data has type universal reference it either holds lvalue or rvalue
template<typename T> void setdata(T && data) ;
所以,右值引用不是右值,事情可能会出错
Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics
如果你错过了使用 std::move()
Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
{
// Derived-specific stuff
}
正确的版本是:
Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
// Derived-specific stuff
}
还有
- 创建对左值的左值引用 - OK
- 创建对右值的右值引用 - OK
- 创建对右值的左值 const 引用 - OK
- 创建对右值的左值引用 - 编译错误