RValue References¶

  • rvalue (right hand value) is an unnamed temporary
  • T&& is used to denote an rvalue reference a reference that can only bind to a temporary
string str = "Hello"s;
string&& ref = str;

input_line_10:3:10: error: rvalue reference to type 'basic_string<...>' cannot bind to lvalue of type 'basic_string<...>'
string&& ref = str;
         ^     ~~~
string&& ref = "Hello"s;
  • A temporary value is safe to consume
  • Useful to avoid copies
  • A constructor taking the class type by rvalue reference is known as a move constructor
    • Similar to a copy constructor but it consumes it's argument
class movable {
    int* _some_data;
public:
    movable(movable&& x) noexcept : _some_data{x._some_data} // consume x
    { x._some_data = nullptr; } // leave x destructible

    //...
};

Return Value Optimization¶

  • Return value optimization (RVO) or copy elision avoids a copy (or move) on return by constructing the result in place
  • RVO applies to local named values and rvalue results
  • Allowed optimziation since C++03, required by C++17
instrumented f() {
    instrumented x;
    return x;
}

instrumented y = f();
  • Arguments to functions are in the caller scope
  • RVO applies to passing an argument by value
void g(instrumented x) { }
g(f());
  • RVO does not apply to returning value argument
  • C++11 defines returning a value argument as a move operation
instrumented h(instrumented x) {
    return x;
}

instrumented z = h(f());

Using RValue Refs and RVO to Avoid Copies¶

Make Classes Movable¶

  • Provide a move constructor and move assignment operator
    • Compiler will provide them implicitely if
      • there are no user declared copy constructors, copy assignment operators, or destructors
      • all non-static data members and base classes are movable
    • To ensure you have them, declare them = default
    • Move constructor and move assignment should be declared noexcept
    • Post-condition of moved from object is partially formed & can alias rhs for move assignment
    • Otherwise can assume no aliasing

Example:

class example_01 {
    int* _data;
public:
    explicit example_01(int x) : _data(new int(x)) { }
    ~example_01() { delete _data; }

    example_01(const example_01&) = delete;
    example_01& operator=(const example_01&) = delete;

    example_01(example_01&& x) noexcept : _data(x._data) { x._data = nullptr; }
    example_01& operator=(example_01&& x) noexcept {
        delete _data;
        _data = x._data;
        x._data = nullptr;
        return *this;
    }

    explicit operator int () { return *_data; }
};
class example_02 {
    unique_ptr<int> _data;
public:
    explicit example_02(int x) : _data(make_unique<int>(x)) { }
    // implicit dtor

    // implicit deleted copy-ctor and copy-assignment

    /*
        move-ctor and move-assignment would be provided by default, but declaring
        them ensures they are provided and correct.
    */
    example_02(example_02&&) noexcept = default;
    example_02& operator=(example_02&&) noexcept = default;

    explicit operator int () { return *_data; }
};

The Self Swap Problem¶

What is the post condition of:

string s = "Hello World!";
swap(s, s);
cout << s << endl;

std::swap() is defined as:

template <class T>
void swap(T& a, T& b) {
    T tmp = move(a);
    a = move(b); // if a and b alias, then b has been moved from
    b = move(tmp);
}
example_01& operator=(example_01&& x) noexcept {
        delete _data;
        _data = x._data;
        x._data = nullptr;
        return *this;
    }

Is this okay?

example_01 e1(42);
swap(e1, e1);
cout << static_cast<int>(e1) << endl;

Class Break - resume here 2018-02-07

Use Return Values, Not Out Arguments¶

  • Out-arguments defeat RVO
void output_01(instrumented& out) {
    instrumented tmp;
    // fill in tmp
    out = tmp;
}

instrumented a1;
output_01(a1);
instrumented output_02() {
    instrumented tmp;
    // fill in tmp
    return tmp;
}

instrumented a2 = output_02();
Further Reading¶

Stop Using Out Arguments

Pass sink arguments by value and return, or swap or move into place¶

  • A sink argument is an argument whose value is returned or stored
  • Most constructor arguments are sink arguments
  • The argument to assignment is a sink argument
string append(string str, const char* suffix) {
    str += suffix;
    return str;
}
instrumented append(instrumented str) {
    // modify str
    return str;
}

auto str = append(instrumented());
class example_03 {
    instrumented _member;
public:
    explicit example_03(instrumented data) : _member(move(data)) { }
};

example_03 e_03{instrumented()};

std::move() is a cast to an rvalue reference.

class example_04 {
    instrumented* _member;
public:
    example_04() : _member(new instrumented()) { }
    ~example_04() { delete _member; }

    example_04(const example_04& x) : _member(new instrumented(*x._member)) { }
    example_04(example_04&& x) : _member(x._member) { x._member = nullptr; }

    // this assignment handles both move and copy
    example_04& operator=(example_04 x) noexcept {
        delete _member;
        _member = x._member;
        x._member = nullptr;
        return *this; }
};
example_04 e41;
example_04 e42;
e41 = e42; // copy
e41 = move(e42);
Advantages to by-value assignment¶
  • Single implementation for copy and move assignment
  • Transactional (strong exception guarantee) for copy assignment
  • Handles self-copy (and usually self-move in moved from case)
Disadvantages¶
  • Potential, significant, performance loss on copy assignment
    • Howard Hinnant - Everything You Ever Wanted To Know About Move Semantics

Rvalue Member Functions¶

  • this is a hidden argument to a member function
  • *this may be an rvalue
class example_05 {
    instrumented _name;
public:
    const instrumented& name() const { return _name; }
};

auto name_01 = example_05().name();
class example_06 {
    instrumented _name;
public:
    const instrumented& name() const& { return _name; }
    instrumented name() && { return move(_name); }
};

auto name_02 = example_06().name();

Forward Declare Argument and Result Type¶

  • You can use pointer or reference to an incomplete type in an interface
  • You can also use an incomplete type as a value argument and result type
class example_07; // forward declaration

const example_07& function_01(const example_07&); // You know this works
example_07 function_02(example_07); // This works also!

Issue¶

  • Assigning an expression to a temporary can impose a performance penalty
auto v1 = g1(f());
auto tmp2 = f();
auto v2 = g1(tmp2);
auto tmp3 = f();
auto v3 = g1(move(tmp3));

Summary Recommendations¶

  • Make classes movable
  • Use return values, not out arguments
  • Pass sink arguments by value and move into place or return
  • Forward declare argument and result types

Homework¶

  • Apply one or more of the recommendations to code in your product
  • Measure the results:
    • Runtime performance of an optimized build
    • (and/or) Binary size of an optimized build
    • (and/or) Compile time of a debug build
    • (and/or) Resulting source line count delta (indicator of readability)
  • Report the results on the class wiki: git.corp.adobe.com/better-code/class/wiki