Modern C++
Standard Practices from Standard Library


2. Move Semantics and Smart Pointers


Stefano Lusardi, Software Developer

Agenda


  • Smart Pointers ( Ownership )
  • Move Semantics ( lvalue vs. rvalue )

Ownership

The owner is responsible for the management of a given resource (i.e. thread or file handle)

The main problem arises on memory deallocation.

Golden rule:
Don't allocate things on the Heap if you don't have to.

Always prefer *Stack allocations with respect to **Heap allocations.


*Automatic object deallocation occurs going out of scope.
You do not need to take care of that.

**Object deallocation needs to be handled in some ways.
You do must take care of that.

Stack vs. Heap example



Foo foo; 
// Stack allocation. No need for manual cleanup.


Foo* foo = new Foo(); 
// Heap allocation. Needs to be deleted at some point.
...
delete foo; 
// Delete operation is required to avoid a Memory Leak
						

So, why do we need Heap allocations?
Polymorphism and much more.


class Shape { virtual ~Shape() = default;};
class Circle : public Shape { /* ... */ };
class Square : public Shape { /* ... */ };

Shape* circle = new Circle();
Shape* square = new Square();
// ... 
delete circle;
delete square;
						

At some point you must delete explicitly both circle and square objects in order to avoid memory leaks.
Here, we can safely assume that calling delete on both circle and square objects is our duty.

A real case scenario


Unfortunately things in the real life looks always more complicated:

						
ShapeManager* manager = new ShapeManager();
Point2d* pointA  = manager->CreatePoint2d(0, 0);
Point2d* pointB  = manager->CreatePoint2d(1, 1);
Segment* segment = manager->CreateSegment(pointA, pointB);
// ... delete?
						

Who is in charge to call delete on these Heap-allocated objects?

Note that the ownership hierarchy on the previous example is not clear at all:
You must read each class implementation to understand the objects hierarchy.


Resource management is not trivial.

  • Can we just suppose a parent-child relationship?
    (e.g. manager owns segment, segment owns pointA and pointB)
  • Or a global manager ownership? (manager owns everything)
  • Or not even ownership at all? (no one owns anything)

And of course pay attention not to delete an object two times.
Note that even the order of delete operations matters.

So, why do we care?

Readability, Testability, Expressivity.
... and of course bugs and crashes!

Does the modern standards provide any utility for (smart) resource management?

Smart Pointers is the answer to our question.

Smart Pointers

From C++11 the standard library provides 3 different utility classes that helps dealing with memory management:

  • std::unique_ptr<T>
  • std::shared_ptr<T>
  • std::weak_ptr<T>

The common idea is that each class provides a wrapper around a pointer to a class T.

All the "Smart Pointer" classes mimic the behavior of a "Raw Pointer" (also providing same syntax)

A great fatures is the so-called "Reference counting" mechanism that allows automatic deallocation of the inner pointer as soon as its last reference gets deleted.

Reference Counting


Roughly speaking it is the number of instances that have a reference to the same object

It is safe to assume that is possible to delete an object when it is no more referenced by any other objects (i.e. it reference count is zero)

Reference Counting


This technique is commonly used in managed languages such as Python, Java and C# in which the memory management is not directly handled by the programmer.

Completely transparent for the user, with nearly zero overhead.

std::unique_ptr

  • Expresses unique ownership of the wrapped pointer
  • It will delete the inner raw pointer going out of scope
  • It does not allow copies (move operations are allowed)
  • It can pass (or better "move") the object ownership to another std::unique_ptr, making it its new (and unique) owner

std::unique_ptr examples



#include <memory>

// Ordinary initialization
std::unique_ptr<Shape> smartCircle(new Circle());

// Polymorphic creation using thread-safe helper function
auto smartSquare = std::make_unique<Square>();

// Move assignment
auto smartestSquare = std::move(smartSquare);
						

std::shared_ptr

  • Expresses shared ownership of the wrapped pointer
  • Use it to assign the same raw pointer to multiple owners
  • The inner raw pointer will be deleted when the last shared_ptr owners goes out of scope
  • Copying a shared_ptr will increase the reference count, viceversa deleting or going out of scope will decrease it

std::shared_ptr examples



Square* square = new Square();
{
    std::shared_ptr<Square> smartSquare1(square);
}
std::shared_ptr<Square> smartSquare2(square);
// crash! (double delete on square pointer)


auto smartCircle = std::make_shared<Circle>();
Circle* circle = smartCircle.get(); // get the inner pointer
// to be avoided, but legal
						

std::weak_ptr

  • "Read-only" version of shared_ptr
  • It gives access to an object that is owned by one or more shared_ptr, without incrementing its reference counter
  • It is useful to break circular references between shared_ptr instances

std::weak_ptr examples



struct Node{
   std::vector<std::shared_ptr<Node>> mChildNodes;
   std::weak_ptr<Node> mParentNode;
};
// can hold a pointer to the parent object 
// in a tree-like structure avoiding a dangling reference


auto smartSquare = std::make_shared<Square>();
std::weak_ptr<Square> weakSquare;
auto weakSquare  = smartSquare; // assign shared_ptr to weak_ptr
auto innerSquare = smartSquare.lock();
// can be converted back to the original shared_ptr
						

Smart Pointers F.A.Q.

Shall I pass by reference?


Yes.
Note that passing by value will generate a copy that will increase the reference count

Shall I get the internal pointer?


Usually not but in several real world applications we often must do that (e.g. QObject::connect)
There is no automatic conversion between smart and raw pointers

Helper functions std::make_unique, std::make_shared, can't access your class private constructor.


Use explicit initialization

Beware of Ownership Stealing policies.


Sometimes the ownership is transferred and double deletion (i.e. crash) may occur.

lvalues  vs.  rvalues

An lvalue is an object or expression whose address can be taken.


  • It is possible to locate an lvalue in a piece of memory
  • It is possible to make an assignment to an lvalue

lvalue example



std::string x;
x = "42"s; // x is an lvalue
						

In fact it is possible both to take its address and to assign a value to it

An rvalue is an unnamed object or expression...
which is not an lvalue.


  • It is not possible to locate an rvalue in a piece of memory
  • It is possible to assign an rvalue to an lvalue

rvalue example


std::string y;
y = "42"s + "43"s; // the expression: "42"s + "43"s is an rvalue
						

  • The expression address cannot be taken
  • The value exists only during the expression evaluation (destroyed after assignment)
  • It is possible to assign the expression result to an lvalue

Another rvalue example


std::string getValue () {
    return "Fourty-Two"s;
}
...
std::string s = getValue(); 
						

  • The string returned by getValue() is an rvalue
  • In fact it is not possible to take its address
  • But it can be assigned to an lvalue

Note that whenever there is an assignment we have a useless copy

So, can't we avoid useless copies and boost performances?


From C++11 it is possible to "use" (or better, "consume") rvalues in order to avoid useless copies.


Remeber that last time we discussed about RVO (Return Value Optimization), which was an implicit technique implemented by default in modern compilers to avoid copies on return values.
In the next section we are going to see how to make this explicitly, not only applicable for return values.

Move Semantics

What does it really means to move an object?

Starting from C++11 the STL introduces std::move(). It is just a static cast to an rvalue refenrece:

It is now possible to convert an lvalue into a temporary object (rvalue) avoiding useless copies


Foo myFoo;

auto myRvalueFoo1 = std::move(myFoo);

auto myRvalueFoo2 = static_cast<Foo&&>(myFoo); 

// equivalent definitions
						

"It's going to die anyway" state of mind.

After you move an object it is going to be in a non-valid state

This is the key to get optimal benefit from move semantics: move all the time you do not need the temporary any longer.

lvalue vs. rvalue move example


void foo(int&  i) { std::cout << "lvalue"; }
void foo(int&& i) { std::cout << "rvalue"; }

int main(){
    int i = 42;
    foo(i);    // lvalue called
    foo(42);   // rvalue called

    foo(std::move(i));  // rvalue called

    return 0;
}
						

Move Constructor and Move Assignement example


class Foo {
    int* value = nullptr;
	
public:    
	Foo(Foo&& other) { // Note non-const &&
        value = other.value;
        other.value = nullptr;
    }
	
    Foo& operator=(Foo&& other) {
        if(this != &other) { // Prevent self-destruction
            if(value) { delete value; }
            value = other.value;
            other.value = nullptr;
        }
		return *this;
    }
	
    ~Foo() {
        if(value) { delete value; }
    }
};
						

"Smart" Move Constructor and Move Assignement example


class Foo {
    // unique_ptr already embeds move semantics behavior
    std::unique_ptr<int> value = nullptr;

public:
    Foo(Foo&& other) = default;
	
    Foo& operator=(Foo&& other) = default;
	
    ~Foo() = default;
};
						

Usage example


struct Bar {
    Bar(std::unique_ptr<int>& v) : barValue{std::move(v)} { }
    std::unique_ptr<int> barValue;
};

int main() {
    auto value = std::make_unique<int>(42);
	
    std::cout << *value.get(); // OK
	
    // Now bar has unique ownership of value
    auto bar = Bar(value); 
    
    // value is in a non-valid state (nullptr)
    std::cout << *value.get(); // Crash!
	
    return 0;
}
						

To avoid such a situation call std::make_unique<int> directly in Bar constructor

Move operations into STL container


std::string str = "Hello"; // std::string defines move operations

std::vector<std::string> stringVector;

stringVector.push_back(str); // Copy here

stringVector.push_back(std::move(str)); // NO copy here

// Now str is empty. Do not use it anymore.
						

Note that STL provides overloaded functions for its container in order to avoid useless copies

"Smart" move operations into STL container


class Foo { 
    Foo(int a, std::string b, double c){ /* ... */} 
};

std::vector<Foo> fooVector;

fooVector.emplace(42, "Fourty-Two", 4.2); // NO copy here. 
// emplace provides in-place construction & push_back operations
						

Note that emplace creates the object by means of arguments forwarding.

Thank you!

What's next?


STL Algorithms and Lambda Expressions

Questions?