欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++备忘录062:Corner Cases for std::move/std::forward gist of "Effective Modern C++"

程序员文章站 2024-02-29 08:38:04
...
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

  1. the type of the local object is the same as that returned by the function

  2. 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);
  1. When an lvalue is passed as an argument, T is an lvalue reference

  2. 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.

相关标签: C++