2.5.5 Smart Pointers

Memory allocation in C++ is a tough subject, dynamic allocation being the hardest part.

Before C++11, the programmer had to be very careful with the life span of dynamically allocated memory in order to, first, be sure that it is released at some point and, second, that no access is made to it once it has been released, not even another release.

See how many problems could occur with this short snippet:

struct foo 
{ 
  foo(int* p) 
    : m_p(p) 
  {} 
 
  foo() 
  { 
    delete m_p; 
  } 
 
private: 
  int* m_p; 
}; 
 
void bar() 
{ 
  foo f1(new int); 
  foo f2(f1); 
 
  int* p1(new int); 
}

There are two problems with this code. First, foo does not define nor disable its copy constructor, so, by default, its m_p pointer will be copied to the new instance. Then, when the original instance and its copy will be destroyed, both will call delete on the same pointer. This is what will happen with f1 and its copy f2. In the best scenario the program would crash here.

The second problem is p1. This pointer points to a dynamically allocated int for which no delete is written. When the pointer will go out of scope then there will be no way to release the allocated memory.

Self-Deleting Non-Shared Pointer

PIC <memory>

C++11 introduces a pointer wrapper named std::unique_ptr, which has the merit of automatically calling delete on the pointer upon destruction. Applied to the previous example, it would solve one problem and force us to find a solution for the other:

struct foo 
{ 
  foo(std::unique_ptr<int> p) 
    : m_p(std::move(p)) 
  {} 
 
  // The default destructor does the job. 
 
private: 
  std::unique_ptr<int> m_p; 
}; 
 
void bar() 
{ 
  std::unique_ptr<int> p1(new int); 
 
  foo f1(std::unique_ptr<int>(new int)); 
  foo f2(std::move(f1)); 
}

This program is undoubtedly safer than the previous one. The copy constructor of foo is still not defined, but it is for sure deleted since std::unique_ptr has no copy constructor neither. So, by default, we cannot share the resource between two instances, which is great. The only solution here is to either allocate a new int for f2 or steal the one from f1. The latter is implemented here.

Then, for the release of p1, it is automatically done when the variable leaves the scope, so no memory is leaked.

One of the best features of std::unique_ptr is the possibility to use a custom deleter to release the pointer. This makes this smart pointer a tool of choice when using C-like resources.

Check for example the use case of libavcodec’s AVFormatContext. The format context is obtained via a call to avformat_open_input(AVFormatContext**, const char*, AVInputFormat*, AVDictionary**) and must be released by a call to avformat_close_input(AVFormatContext**). With the help of std::unique_ptr this could be done as follows:

namespace detail 
{ 
  static void close_format_context(AVFormatContext* context); 
} 
 
void foo(const char* path) 
{ 
  AVFormatContext* raw_context_pointer(nullptr); 
 
  const int open_result = 
    avformat_open_input 
      (&raw_context_pointer, path, nullptr, nullptr); 
 
  std::unique_ptr 
  < 
    AVFormatContext, 
    decltype(&detail::close_format_context) 
  > 
  context_pointer 
  (raw_context_pointer, &detail::close_format_context); 
 
  // }

With this approach, the format context will be released via a call to detail::close_format_context as soon as context_pointer leaves the scope. Do we know if the memory pointed by raw_context_pointer is dynamically allocated? We do not, and it does not matter. What we have here is a simple robust way to attach a release function to an acquired resource.

Self-Deleting Shared Pointer

PIC <memory>

I have a hard time trying to find a use case where std::shared_ptr is the best solution, so let’s just focus on a good-enough solution.

This smart pointer is the answer to the problem of having a dynamically allocated resource that may outlive its creator, and that may also be accessed from two independent owners. In this case, the ownership of the resource is unclear, as it is shared, so the idea is to keep the resource alive until all its owner release it.

For example, let’s say we have a function whose role is to dispatch a message to multiple listeners, whom will not process it right now. For some reason the message cannot be copied, so we cannot send a copy of it to everyone:

void dispatch_message 
(const std::vector<listener*>& listeners, 
 const std::string& raw) 
{ 
  message m = parse_message(raw); 
  std::shared_ptr<message> p 
    (std::make_shared<message>(std::move(m))); 
 
  for(listener* l : listeners) 
    listener->add_to_queue(m); 
}

With this implementation the message outlives the scope of dispatch_message() and will remain in memory until all listeners have released it.

Is it the best solution for this problem? Honestly I doubt that. Actually, most uses of std::shared_ptr I have seen, including mine, looked a bit like a lack of reasoning about resource management.

Wouldn’t it have been better if the dispatching and the listeners were scheduled in a loop and if the dispatcher would own the messages for some iterations, or until they would be marked as processed by the listeners? Do we really have to pay for dynamic allocations here?

Guideline

When you find yourself thinking that a shared resource with no clear life span — a std::shared_ptr — is a good answer to your problem, please double check your solution, and ask for a second opinion.

It’s a trap

Many documentations declare, rightfully, that std::shared_ptr is thread-safe, and I have seen people using it to safely access the allocated resource in a concurrent way.

It must be said that the only thread-safe part in a std::shared_ptr is its reference counter.

Nothing is done — nothing can be done — to provide an automatic thread-safe access to the allocated resource. Actually, even having two threads doing respectively a copy (i.e. std::shared_ptr<T> p(sp)) and a deletion (i.e. sp.release()), for the same shared pointer sp, has no defined outcome without additional synchronization. The only guarantee is that the increment of the counter in the copy won’t be done between the decrement and the deletion from the release.

Shared Pointer Observer

PIC <memory>

What happens when the instance pointed by a std::shared_ptr owns a std::shared_ptr pointing to the owner of the former? Then we have a cycle and none of the instances will be released.

struct foo; 
 
struct bar 
{ 
  std::shared_ptr<foo> m_foo; 
}; 
 
struct foo 
{ 
  std::shared_ptr<bar> m_bar; 
}; 
 
void foobar() 
{ 
  std::shared_ptr<foo> f(std::make_shared<foo>()); 
  std::shared_ptr<bar> b(std::make_shared<bar>()); 
  b->m_foo = f; 
  f->m_bar = b; 
 
  // The instances pointed by foo and foo->bar won’t be deleted. 
}

In order to break this kind of dependency loop, one pointer should be declared as an std::weak_ptr. Just like std::shared_ptr, this smart pointer is here to point to a shared resource, except that it does not count as an owner of the resource. Additionally, it provides a way to test if the resource is still available via the std::weak_ptr::lock() function, which returns a shared pointer on the resource if it is available, or nullptr otherwise.

struct foo; 
 
struct bar 
{ 
  std::weak_ptr<foo> m_foo; 
}; 
 
struct foo 
{ 
  std::shared_ptr<bar> m_bar; 
}; 
 
void foobar() 
{ 
  std::shared_ptr<foo> f(std::make_shared<foo>()); 
  std::shared_ptr<bar> b(std::make_shared<bar>()); 
  b->m_foo = f; 
  f->m_bar = b; 
 
  { 
    std::shared_ptr<foo> resource(b->m_foo.lock()); 
    if (resource) 
      printf("This message is printed.' n"); 
  } 
 
  f.reset(); 
 
  std::shared_ptr<foo> resource(b->m_foo.lock()); 
 
  if (resource) 
    printf("This message is not.' n"); 
}