从 r-value ref-qualified 方法移动,还是不移动?
To move, or not to move from r-value ref-qualified method?
在下面的 C++11+ 代码中,哪个 return 语句构造应该是首选?
#include <utility>
struct Bar
{
};
struct Foo
{
Bar bar;
Bar get() &&
{
return std::move(bar); // 1
return bar; // 2
}
};
嗯,因为它是一个 r-value ref 合格的成员函数,this
大概即将过期。所以将 bar
移出是有意义的,假设 Bar
实际上从移动中获得了一些东西。
由于 bar
是一个成员,而不是本地 object/function 参数,因此 return 语句中复制省略的通常标准不适用。它总是会复制,除非你明确地std::move
它。
所以我的答案是选择第一个选项。
我想澄清我的观点(来自评论)。尽管移动结果通常比复制更有效,但这不是我主要关心的问题。核心问题源于错误的假设,即通过对 Foo
实例调用者的意图的 r 值引用调用此方法包括创建新的 Bar
值。例如:
Foo Produce_Foo(void);
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
auto bar{Produce_Foo().get()};
// Oops! No one asked us to make a useless temporary...
cout << Produce_Foo().get().value() << endl;
解决方案是添加一个专用函数,仅用于查看存储的栏并控制存储的栏对象的内容。
Bar const & get_bar() const noexcept
{
return bar;
}
// no particular need to this method to be r-value reference qualified
// because there is no direct correlation between Foo instance being moved / temp
// and intention to take control over content of stored bar object.
Bar give_bar() noexcept
{
return ::std::move(bar);
}
既然用户有了选择,就不会有更多问题了:
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
// There is also no need to figure out whether Produce_Foo returned an rvalue or not.
auto bar{Produce_Foo().give_bar()};
// Alright, no extra temporaries.
cout << Produce_Foo().get_bar().value() << endl;
至于右值引用限定方法的用例,我认为它们在处理与此对象相同类型的临时对象时最有用。例如string class 实现这种连接运算符可以减少
重新分配,本质上就像一个专用的字符串生成器。
我更喜欢选项 3:
Bar&& get() &&
// ^^
{
return std::move(bar);
}
而且,当我们这样做的时候:
Bar& get() & { return bar; }
Bar const& get() const& { return bar; }
Bar const&& get() const&& { return std::move(bar); }
我们是右值,所以它应该可以自由蚕食我们的资源,所以 move
-ing bar
是正确的。但是仅仅因为我们愿意移动 bar
并不意味着我们必须强制执行这样的移动并导致额外的操作,所以我们应该 return 一个右值引用它。
这就是标准库的工作方式 - 例如std::optional<T>::value
.
在下面的 C++11+ 代码中,哪个 return 语句构造应该是首选?
#include <utility>
struct Bar
{
};
struct Foo
{
Bar bar;
Bar get() &&
{
return std::move(bar); // 1
return bar; // 2
}
};
嗯,因为它是一个 r-value ref 合格的成员函数,this
大概即将过期。所以将 bar
移出是有意义的,假设 Bar
实际上从移动中获得了一些东西。
由于 bar
是一个成员,而不是本地 object/function 参数,因此 return 语句中复制省略的通常标准不适用。它总是会复制,除非你明确地std::move
它。
所以我的答案是选择第一个选项。
我想澄清我的观点(来自评论)。尽管移动结果通常比复制更有效,但这不是我主要关心的问题。核心问题源于错误的假设,即通过对 Foo
实例调用者的意图的 r 值引用调用此方法包括创建新的 Bar
值。例如:
Foo Produce_Foo(void);
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
auto bar{Produce_Foo().get()};
// Oops! No one asked us to make a useless temporary...
cout << Produce_Foo().get().value() << endl;
解决方案是添加一个专用函数,仅用于查看存储的栏并控制存储的栏对象的内容。
Bar const & get_bar() const noexcept
{
return bar;
}
// no particular need to this method to be r-value reference qualified
// because there is no direct correlation between Foo instance being moved / temp
// and intention to take control over content of stored bar object.
Bar give_bar() noexcept
{
return ::std::move(bar);
}
既然用户有了选择,就不会有更多问题了:
// Alright, caller wanted to make a new `Bar` value, and by using `move`
// we've avoided a heavy copy operation.
// There is also no need to figure out whether Produce_Foo returned an rvalue or not.
auto bar{Produce_Foo().give_bar()};
// Alright, no extra temporaries.
cout << Produce_Foo().get_bar().value() << endl;
至于右值引用限定方法的用例,我认为它们在处理与此对象相同类型的临时对象时最有用。例如string class 实现这种连接运算符可以减少 重新分配,本质上就像一个专用的字符串生成器。
我更喜欢选项 3:
Bar&& get() &&
// ^^
{
return std::move(bar);
}
而且,当我们这样做的时候:
Bar& get() & { return bar; }
Bar const& get() const& { return bar; }
Bar const&& get() const&& { return std::move(bar); }
我们是右值,所以它应该可以自由蚕食我们的资源,所以 move
-ing bar
是正确的。但是仅仅因为我们愿意移动 bar
并不意味着我们必须强制执行这样的移动并导致额外的操作,所以我们应该 return 一个右值引用它。
这就是标准库的工作方式 - 例如std::optional<T>::value
.