C++备忘录062:Corner Cases for std::move/std::forward gist of "Effective Modern C++"
A sample implementation of std::move
, which better be memorized as rvalue_cast
template <typename T>
decltype(auto) move(T&& param) {
return static_cast<std::remove_reference_t<T>&&>(param);
}
Don’t declare objects const
if you want to be able to move from them
#include <string>
struct S {
S(const std::string text)
: value(std::move(text)) {}
std::string value;
}
std::string
was copied, not moved. The move constructor takes an rvalue reference to a non-const std::string
, and std::move(text)
is a const std::string
, it can be passed to the copy constructor, because an lvalue-reference-to-const is permitted to bind to a const
rvalue.
When to use std::move
/std::forward
on return value
Only when a function returns by value, and you’re returning an object bound to an rvalue reference or a universal reference.
Compilers may elide the copying (or moving) of a local object in a function that returns a value if
-
the type of the local object is the same as that returned by the function
-
the local object is what’s being returned
struct S {};
S foo() {
S s;
return std::move(s);
}
gives a warning, because of RVO
moving a local object in a return statement prevents copy elision [-Wpessimizing-move]
struct S {};
S foo(S&& s) {
return std::move(s);
}
is a valid optimization
Standard says that if the condition for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue
The standard requires that when the RVO is permitted, either copy elision takes place or std::move
is implicitly applied to local objects being returned.
S foo() {
S s;
return s;
}
In the worst case scenario, it is treated as
S foo() {
S s;
return std::move(s);
}
S foo(S s) {
return s;
}
must be treated as
S foo(S s) {
return std::move(s);
}
And std::forward
implementation?
template <typename T>
T&& forward(std::remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
template <typename T>
void foo(T&& param);
-
When an lvalue is passed as an argument,
T
is an lvalue reference -
When an rvalue is passed,
T
is a non-reference
If an lvalue Widget widget
passed to foo
, then T
is type Widget&
If an rvalue passed to foo
, then T
is type Widget
reference collaping
If either reference is an lvalue reference, the result is an lvalue reference. Otherwise (i.e., if both are rvalue references) the result is an rvalue reference
In lvalue’s case
Widget& && forward(std::remove_reference_t<Widget&>& param) {
retrun static_cast<Widget& &&>(param);
}
transfers to
Widget& forward(Widget& param) {
return static<Widget&>(param);
}
In rvalue’s case
Widget&& forward(std::remove_reference_t<Widget&&>& param) {
return static_cast<Widget>(param);
}
transfer to
Widget&& forward(Widget& param) { // basically a std::move
return static_cast<Widget>(param);
}
auto&&
folows the same rule
Widget w;
auto&& w1 = w; // lvalue, so Widget& for auto, Widget& && collapses to Widget&
auto&& w2 = widgetFactory(); // rvalue, so auto is Widget, done
Such a beautiful language
When compilers generate a reference to a reference in a reference collapsing context, the result becomes a single reference. If either of the original refer‐ ences is an lvalue reference, the result is an lvalue reference. Otherwise it’s an rvalue reference.