Modern (Effective) C++ - Prefer to use convenience functions when creating unique or shared pointers.

This article is inspired by Item 21 of Effective Modern C++ by Scott Meyers.
C++11 provides a function to create shared_ptr called std::make_shared, The similar version for unique pointer was included in C++14 called std::make_unique.

If you want to use make_unique functionality in C++11 project, here is a quick and dirty version


There are three distinct advantages of using the make functions -

1. Removing the necessity to mention the type twice, leading to more compact code.

2. Making sure that memory is released even when an exception is raised. In the below example, there is a possibility that objects are not freed when exception is thrown.

The output of this program in my laptop is -
In the above example we can use that objects of class A is getting constructed and destructed from id 1 to id 4 ( inclusive), but from task 5 to 8, the object are not getting destructed. Notice I am throwing an exception for these.

The sequence of operations here is
1. Call new A() to create new object of A.
2. Call computeTaskNumber ( which throws exception from 5 to 8)
3. Assign the newly created object to the shared_ptr ( which never gets called due to the exception thrown above).

Hence the memory for objects 5 to 8 lead to the infamous memory leak

However if we use, std::make_shared, we can always be sure that if the object is constructed, then it is destroyed as well.

The only change in the below program is on line number 51, where I have called make_shared instead of using new.

The output of the program is -





We see that objects 5 to 8 never gets created.
The above scenario is same for the usage of std::make_unique as well.

3. This advantage is only for std::shared_ptr.

When we use new , there are two calls for the memory manager. One to create the object, and another call to create the control block.
However if we use std::make_shared, only a single call is made to allocate enough space for both object and the control block.

According to Optimized C++, a single call to the memory manager translates to thousands of CPU instructions. Hence we save a significant amount of time, by making only a single call for memory allocation.

The downside of this occurs when we are use weak pointers in our project.

In this scenario,
  • The object is created using make_shared, hence the memory is allocated for both the object and the control block.
  • There are currently weak pointers pointing to this shared_ptr. ( weak_ref_count > 0)
  • All shared pointers to this object gets destroyed. ( shared_ref_count becomes 0)
  • The object gets destroyed.
  • Since weak reference count is greater than 1, we cannot free the control block.
  • But since the memory is allocated using make_shared ( which allocates space for both object and control block), we can not free up the space of the deleted raw object.
  • The memory is only reclaimed once all the weak pointers are destroyed (weak ref counter = 0).

However if we had used new, the memory corresponding to the object could have been reclaimed as soon as shared_ref_count hit 0.

On a final note, we cannot use make_shared or make_unique when we are using custom deleters. To circumvent the problem we can write the code like this -

However this is a bit inefficient, since when the shared_ptr is sent to funcA, the reference count is manipulated atomically. So we can modify our code in the following fashion -

std::move converts lvalue to rvalue object (reference count is not manipulated). aObj then becomes nullptr.

PS: There is another version of shared pointer called std::allocate_shared, which uses custom allocators. More about it in this link.
Modern (Effective) C++ - Prefer to use convenience functions when creating unique or shared pointers. Modern (Effective) C++ - Prefer to use convenience functions when creating unique or shared pointers. Reviewed by zeroingTheDot on February 06, 2018 Rating: 5

No comments:

Powered by Blogger.