C++ and move semantics and perfect forwarding

Move semantics makes it possible for compilers to replace expensive copying operations with less expensive moves. In the same way that copy constructors and copy assignment operators give you control over what it means to copy objects, move constructors and move assignment operators offer control over the semantics of moving. Move semantics also enables the creation of move-only types, such as std::unique_ptr, std::future, and std::thread.

Perfect forwarding makes it possible to write function templates that take arbitrary arguments and forward them to other functions such that the target functions receive exactly the same arguments as were passed to the forwarding functions.

At runtime, neither does anything at all. They generate no executable code. Not a single byte.

What happens during swap method

template<typename T>
void foo(T& a, T& b) {
    auto tmp = a;
    a = b;
    b = tmp;
}

What happens:

copy swap variant cpp

What we expect to happen:

move swap variant cpp

Observations:

  • no need to copy contents from a to tmp
  • no need to copy contents from b to tmp
  • a’s copy inside tmp is no longer needed at the end

We need:

  • a way to move the internal states of objects
  • a way to express „this is object is temporary/can be recycled”
  • to overload funtions / constructors / … for such temporaries

std::move

After using std::move we expect to have some perfomance gain if T is cheaply movable.

Almost all standard containers are cheap to move (std::array is not).

template<typename T>
void foo(T& a, T& b) {
    auto tmp = std::move(a);
    a = std::move(b);
    b = std::move(tmp);
}

lvalue and rvalue

lvalue = expression of which we can take its memory address

rvalue = expression of which we cannot take its memory address

Original meaning: (l/r) values on (left/right) hand side of assignment

lvalue vs rvalue cpp
auto x = std::make_unique<int>(5);
std::unique_ptr<int> y;
y = std::move(x);
unique ptr move cpp

rvalue references

lvalue references:

  • T& – bind to non-const lvalues of type T only
  • T const& – bind to const lvalues and rvalues of type T

rvalue references:

  • T&& – bind to rvalues of type T only

std::move and rvalue

So now we can say that std::move casts an expression to an rvalue.

void foo(int& x);
void bar(int const& x);
void baz(int&& x);

int i = 0;

foo(i);           // OK
foo(std::move(i)) // ERROR: lvalue ref cannot bind to rvalue

bar(i);           // OK
bar(move(i));     // OK

baz(i);           // ERROR: rvalue ref cannot bind to lvalue
baz(std::move(i));// OK

Overview of Special Member Functions

class T {
    T();                            // Default Constructor

    T(std::initializer_list<...>);  // Initializer List Constructor

    ~T();                           // Destructor

    T(T const&);                    // Copy Constructor
    T& operator=(T const&);         // Copy Assignment Operator

    T(T&&);                         // Move Constructor
    T& operator=(T&&);              // Move Assignment Operator
}

std::forward

The story for std::forward is similar to that for std::move, but whereas std::move unconditionally casts its argument to an rvalue, std::forward does it only under certain conditions. std::forward is a conditional cast.

std::forward casts its argument to an rvalue only if that argument is bound to an rvalue.

Reference:

https://hackingcpp.com/cpp/lang/move_semantics.html

Other cpp articles from my site:

https://mateuszrzeczyca.pl/c-and-smart-pointers-my-notes/

https://mateuszrzeczyca.pl/c-and-shallow-copy-vs-deep-copy/