Sunday, August 14, 2016

C++11’s Smart Pointers - Automatic memory management

Introduction:
There are three kinds of smart pointers in c++ 11.
  1. Shared pointer (shared_ptr)
  2. Weak pointer (weak_ptr)
  3. Unique pointer (unique_ptr)

  • They should be used only with heap memory, means only new constructor. if you use them with stack memory, a runtime error will occur.
  • They auto delete the object once they go out of scope (except weak pointer)
  • Avoid using raw pointers to refer to the same object. Don’t mix them together.
  • All are listed in <memory>

Shared pointer
  • Implements shared ownership, means any number of these smart pointers jointly own the object. The owned object is destroyed only when its last owning smart pointer is destroyed.
  • Once the managed object is gone, the shared_ptr = nullptr or 0.
  • A function can take and return a shared_ptr as value.
shared_ptr<Thing> do_something (shared_ptr<Thing> p)

  • reset() or nullptr: Decrements the reference count and delete the pointed-to object if required and then set shared_ptr = nullptr
class Thing {
public:
void func();
}
void foo () {
shared_ptr<Thing> p1 {new Thing}; // p1 owns Thing
shared_ptr<Thing> p1 = p1; // p1 and p2 share the ownership of Thing
p1 -> func(); //call member function like built in pointer
cout <<  *p1 // dereference just like built in pointer
p1.reset(); // decrement shared_ptr count, delete if required
p2 = nullptr; // decrement shared_ptr count, delete if required
}

  • How to get raw pointer from shared_pointer (Don’t do it, dangerous)
shared_ptr<Thing> p1 = make_shared<Thing>(); //same as new, but efficient
Thing *raw_ptr = p1.get();
Thing *raw_ptr = sp; //Error

  • Inheritance and shared_ptr
class Base { };
class Derived: public Base{ };
shared_ptr<Derived>  dp1 {new Derived};
shared_ptr<Base>  bp1 = dp1;
shared_ptr<Base>  bp2 {dp1};
shared_ptr<Base>  dp1 {new Derived};

  • Casting shared_ptr
shared_ptr<Base>  base_ptr  {new Base};
shared_ptr<Derived>  derived_ptr;
derived_ptr = static_pointer_cast<Derived> (base_ptr)

Other casting functions: static_pointer_cast, dynamic_pointer_cast, const_pointer_cast

  • Better alternative of new function
  • shared_ptr<Thing> p1 = new Thing(32, “hello”); //inefficient
Two memory allocation, one for Thing object and one for manager object
created by the shared_ptr construction

  • shared_ptr<Thing> p1 = make_shared<Thing>(32, “hello”);
same as new, but efficient. Only one memory allocation that is big enough to hold both the manager object and new object.

Weak Pointer
  • Weak pointers just observe the managed object. They do not keep it alive or affect its lifetime. So even if the last weak_ptr goes out of scope or disappear, the pointed-to object can still exist.
  • weak_ptr does not support * or -> (no dereference allowed).
Neither you can access the pointer to the object with it (No get() function available)
  • weak_ptr can be used to determine whether the object exists and to generate a shared_ptr that can be used to refer to it. Using lock() function.
Example:
void do_something(weak_ptr<Thing> wp) {
shared_ptr<Thing> sp = wp.lock(); //get shared_ptr from weak_ptr
if(sp) { //do your stuff }
else { // Thing object is gone }
}

  • Initializing a weak_ptr
    • Default value is empty
    • You can point a weak_ptr to an object only by copy or assignment from a shared_ptr or an existing weak_ptr to the object.
    • Unlike shared_ptr, you can not reset a weak_ptr by assignment to nullptr. Use reset() function to set a weak_ptr back to the empty state in which it is pointing to nothing.

shared_ptr<Thing> sp1 {new Thing};
weak_ptr<Thing> wp1 {sp1}; //construct wp1 from a shared_ptr
weak_ptr<Thing> wp2; // empty weak_ptr pointing to nothing
wp2 = sp1; // wp2 now points to new Thing object
weak_ptr<Thing> wp3 {wp2}; //construct wp3 from a weak_ptr
weak_ptr<Thing> wp4;
wp4 = wp2; //wp4 also now points to the same new Thing object

  • Get shared_ptr from weak_ptr / Check if shared_object exist or not?
shared_ptr<Thing> sp = wp.lock(); //get shared_ptr from weak_ptr

Note: You can not refer to the object directly with a weak_ptr (already
mentioned in the second bullet of this weak_ptr section), you need to get a shared_ptr from it first with lock() function (already mentioned in the fourth bullet of this weak_ptr section).

The lock() function examines the state of the manager object to determine whether the managed object still exists, and provides an empty shared_ptr if it does not, and a shared_ptr if it does. See example in the fourth bullet of this weak_ptr section).

  • Key points:
    • weak_ptr does not support * or -> (no dereference allowed).
    • No get() function available
    • lock() function to get shared_ptr
    • No nullptr assignment allowed, use reset() function.

Unique Pointer
  • An object is owned by exactly one unique_ptr.
  • Unlike shared_ptr or built-in pointer, you can not copy or assign a unique_ptr to another unique_ptr.
  • When the unique_ptr goes out of scope, the pointed-to object gets deleted and this happens regardless of how we leave the function, either by a return or an exception being thrown somewhere.

void foo() {
unique_ptr<Thing> p { make_unique<Thing> }; // p owns the Thing
p -> do_something();
another_function(); // might throw an exception
} // p gets destroyed. Destructor destroys the Thing.

  • VVI: Since copy construction is not allowed, if you want to pass a unique_ptr as a function argument, do it by reference.
unique_ptr<Thing> p1 { make_unique<Thing> }; // p owns the Thing
unique_ptr<Thing> p2 {p1}; //error, copy-construction is not allowed
unique_ptr<Thing> p3; //an empty unique_ptr
p3 = p1; //error, copy assignment is not allowed
  • Transferring ownership
    • Create a thing and returns a unique_ptr to it.
unique_ptr<Thing> create_Thing() {
unique_ptr<Thing> tmp_ptr { new Thing }
return tmp_ptr; //tmp_ptr will surrender ownership
}

void foo () {
unique_ptr<Thing> p1 { create_Thing() }; // p1 owns the Thing
unique_ptr<Thing> p2; // default unique_ptr; owns nothing
p2 = create_Thing(); // p2 now owns the second Thing
}

    • Explicit transfer of ownership between unique_ptr using std::move()
unique_ptr<Thing> p1 {new Thing}; // p1 owns the Thing
unique_ptr<Thing> p2; // p2 owns nothing
// invoke move assignment explicitly
p2 = std::move(p1); // now p2 owns it, p1 owns nothing

// invoke move construction explicitly
unique_ptr<Thing> p3 {std::move(p2)}; //now p3 owns it, p1 and p2 own nothing

  • Use reset() and nullptr in the same way as shared_ptr
  • Use make_unique for memory allocation instead of new. It is especially designed for unique_ptr. The reasoning is same for both unique_ptr and shared_ptr.

Reference:
Kieras, D.: Using C++11’s Smart Pointers. University of Michigan. Tutorial (2016) 1 - 14