Move Semantics

Contents

  • auto
  • Return Value Optimization
  • Move Semantics
  • Sink functions
  • Perfect forwarding

auto

Used to denote that a variable has automatic storage.

In C++ all variables have automatic storage by default.

auto

In C++11 it gets a new meaning - tells the compiler to deduce the type of the variable. This is called type inference

int x = 42;
std::string s = "the answer is";

auto y = 2 * x; // 2 is int, int * int = int, so y is int
auto t = s + "!"; // std::string + "!" is string, so t is string

Benefits of auto

  • saves time determing what is the correct type
  • allows to refactor the code by renaming a type
  • allows to change the type as long as it behaves in the same way
BOOL result = ::CreateProcess(...);
if (!result)
{
    DWORD error = ::GetLastError();
    std::cout << "Could not create process " << error
        << std::endl;
}
// don't care what type it is as long as it behaves as a bool
auto result = ::CreateProcess(...);
if (!result)
{
    // don't care what type it is as long as it can be logged
    auto error = ::GetLastError();
    std::cout << "Could not create process " << error
        << std::endl;
}

Bonus: I don't get shouted at

std::vector<int> v;
// ...
auto begin = v.cbegin();
std::vector<int>::const_interator begin2 = v.cbegin();

auto gotchas

  • gets the result by value
struct Course {
    std::vector<int>& get_students() const;
}

// ...

auto students = course.get_students();
course.get_students()[0] = 24;
students[0] = 42;
std::cout << course.get_students()[0] << std::endl;
// it is still 24
// students is actually a copy
auto& students = course.get_students();
course.get_students()[0] = 24;
students[0] = 42;
std::cout << course.get_students()[0] << std::endl;
// it is 42

The difference is only the & after auto

Taking a const reference with auto

const auto& students = course.get_students();

Return Value Optimization

RVO

Values returned from functions are copied in the local variables

std::vector<int> get_random_ints(int count);
// ...
auto numbers = get_random_ints(1000);

You can get advice to not return such expensive to copy things by

value
void get_random_ints(int count, std::vector<int>& numbers);
// ...
std::vector<int> numbers;
get_random_ints(1000, numbers);
  • more code

Optimizations

Compilers are allowed to make any optimization as long as the program behaves in the same way as if there are no optimizations

RVO

C++ allows compilers to implement RVO even breaking this rule

How is RVO implemented by compilers
std::vector<int> get_random_ints(int count);
// ...
auto numbers = get_random_ints(1000);

Gets compiled as

void get_random_ints(int count, std::vector<int>& numbers);
// ...
std::vector<int> numbers;
get_random_ints(1000, numbers);

RVO doesn't always work

std::vector<int> get_numbers() {
    std::vector<int> result;
    // ...
    return (rand() % 2)? result : std::vector<int>();
}

Move

Instead of copying values move them directly into their new variables

Move

In C++ it is easy to create unnecessary copies of object, hurting performance.

std::string s = "Hello";
std::string t = "World";
std::string m = s + t;

s is concatenated with t in a temporary string which is then copied into m

assuming that the compiler will not optimize the code

lvalues and rvalues

  • come from C
  • lvalues are on the left side of operator =
  • rvalues are on the right side of operator =

lvalues and rvalues

  • In C++
  • lvalues are variables - names of the values, storage for the value
  • rvalues are values of expressions

Rule of thumb

  • T&& can be both - lvalue and rvalue
  • If it has has a name it is an lvalue
Derived(Derived&& other) 
  : Base(other) // wrong: other is an lvalue --> copies the Base part
{
    // ...
}
Derived(Derived&& other) 
  : Base(std::move(other)) // :)
{
    // ...
}
  • lvalue reference T&
  • rvalue reference T&&

const T&

Can reference everything - values stored in variables, result values of expressions.

const T&&

Can reference only result values of expressions

void function(const std::string& lvalue);  // lvalue overload
void function(const std::string&& rvalue); // rvalue overload
both declarations, the compiler chooses which overload to callon the type of the string value.
std::string s = "Hello World";
function(s); // lvalue overload

function("Hello World"); // rvalue overload
// the const char* is converted to a temporary string which can be
// rvalue
class String {
public:
    String(const char* s)
        : m_Length(std::strlen(s))
        , m_Buffer(new char[m_Length])
    {
        std::memcpy(m_Buffer, s, m_Length);
    }
    
    ~String() {
        delete [] m_Buffer;
    }
    
private:
    size_t m_Length;
    char* m_Buffer;
};

String representation

Copy constructor

String(const String& o)
    : m_Length(o.m_Length)
    , m_Buffer(new char[m_Length])
{
    std::memcpy(m_Buffer, o.m_Buffer, m_Length);
}

Create a copy of the other string

Copy construction

Move constructor

class String {
    // ...
    String(String&& o)
        : m_Buffer(o.m_Buffer)
        , m_Length(o.m_Length)
    {
        o.m_Buffer = nullptr;
        o.m_Length = 0;
    }
    
String(const String&& o)

Moves the value of the other string into this object

Move construction

String s("Hello");
String t("World");

String s2 = s; // copy
String t2 = s + t; // move

std::move

Tells the compiler to create an rvalue reference for an lvalue. Allows us to move ordinary values stored in variables.

template <typename T>
T&& std::move(T& lvalue);
String s("Hello");
String t = std::move(s); // move s into t

// !!! Undefined behavior !!!
std::cout << s << std::endl;
// We have moved s into t, it's value is unknown

Assignment operator

class String {
    // ..
    
    String& operator= (const String& rhs) {
        if (this != &rhs) {
            delete [] m_Buffer;
            m_Length = rhs.m_Length;
            m_Buffer = new char[rhs.m_Length];
            std::memcpy(m_Buffer, rhs.m_Buffer, m_Length);
        }
        return *this;
    }

Assignment operator

before

Assignment operator

after

Move assignment

class String {
    // ..
    // move assignment
    String& operator= (String&& rhs) {
        if (this != &rhs) {
            delete [] m_Buffer;
            m_Length = rhs.m_Length;
            m_Buffer = rhs.m_Buffer;
            rhs.m_Length = 0;
            rhs.m_Buffer = nullptr;
            rhs.m_Length = 0;
        }
        return *this;
    }
};

Move Assignment

before

Move Assignment

after

Copy or Move?

String s = "Hello";
String t = "World";

String m = s + t; // 1
m = t + s; // 2

m = t; // 3
m = std::move(s); // 4
  1. move
  2. move
  3. copy
  4. move

Move assignment - alternative implementation

class String {
    // ..
    // move assignment
    String& operator= (String&& rhs) {
        std::swap(m_Buffer, rhs.m_Buffer);
        std::swap(m_Length, rhs.m_Length);
        return *this;
    }
};

Move Assignment - alternative implementation

before

Move Assignment - alternative implementation

after

Move Assignment - alternative implementation

in the end

Although the value of an object may be moved, the object is still going to be destoyed (its constructor has been run)

Always leave moved object in a consistent state

So that their destructor will run correctly.

Perfect forwarding

template <typename T, typename Arg1>
T factory(/*const*/ Arg1 /*&*/ arg1)
{
    return T(arg1);
}
  • Arg1 arg1
  • Arg1& arg1
  • const Arg1& arg1

Arg1 arg1

  • will be wrong if T stores a reference/pointer to the value
  • extra copying

Arg1& arg1

  • will be OK if T stores a reference/pointer to the value
  • no extra copying
  • will NOT work for const references and temporaries

const Arg1& arg1

  • will be OK if T stores a const reference/pointer to the value
  • no extra copying
  • will work for const references and temporaries

And now what?

Overloading

template <typename T, typename Arg1>
T factory(Arg1& arg1);

template <typename T, typename Arg1>
T factory(const Arg1& arg1);

// How about
template <typename T, typename Arg1, typename Arg2>
T factory(const Arg1& arg1, const Arg2& arg2);

&&

  • T& + & --> T&
  • T& + && --> T&
  • T&& + & --> T&
  • T&& + && --> T&&

Perfect forwading

template <typename T, typename Arg1>
T factory(Arg1&& arg1)
{
    return T(std::forward<Arg1>(arg1));
}

std::forward

template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
    return static_cast<S&&>(a);
}

http://thbecker.net/articles/rvalue_references/section_01.html