Table of Contents

C - C++ STL - STL Containers and auto_ptrs - Why they do not mix

A common scenario in C++ is to create a container of user defined objects.


Container of "Dumb" Pointers

#include <vector>
 
class Option {
public:
  ..
}
 
void vec_option(std::vector<Option*>& vec)
{
  vec.push_back(new Option());
 
  // .. Some additional code ..
 
  delete vec.back();    // Skipping this line causes a memory leak
  vec.pop_back();       // Causes a dangling pointer if this line isn't reached
}

NOTE: If the code prior to delete vec.back; causes an exception to be thrown or returns from the function, this will lead to a memory leak or a dangling pointer.

  • Why is this?
  • Simply, because although the destructor called by std::vector, it does NOT remove the allocations that were made by the new operator, when creating the Option object.

Containers of auto_ptrs

Although the above code will compile and run, it is bad practice to use such containers of “dumb” pointers due to the fragility of the function.

One such smart pointer is std::auto_ptr<>.

Let's modify the function prototype code above to make use of it:

#include <vector>
#include <memory>   // Needed for std::auto_ptr<>
 
class Option {
public:
  ..
}
 
void vec_option(std::vector<std::auto_ptr<Option> >& vec)
{
  vec.push_back(new Option());
  ..
  vec.pop_back();
}

NOTE: When we try to compile this code we receive a compiler error! What just happened?

  • It all comes down to the contract that std::auto_ptr<> makes with you when you agree to use it in your code.

std::auto_ptr<> does not fulfill the requirements of being copy-constructible and assignable.

  • Unlike objects which do have this requirement, when copying or assigning an auto_ptr the two elements are not logically independent.
  • This is because auto_ptr has semantics of strict ownership and is thus solely responsible for an object during the object's life cycle.
  • If we copy the auto_ptr then the source auto_ptr will lose the reference to the underlying object.

Since objects within an STL container must be copy-constructible and assignable, a compile time error is provided if an auto_ptr is used within a container.

  • Algorithms, such as those involved in sorting STL containers, often copy objects while carrying out their tasks.
  • Hence, there would be a large scope for memory leaks and/or dangling pointers if containers of auto_ptrs were allowed.

Boost Smart Pointers and STL Containers

Prior to the C++11 standard the best way to overcome this problem was to use the Boost library smart pointers.

#include <vector>
#include <boost/shared_ptr.hpp>  // Need to include the Boost header for shared_ptr
 
class Option {
public:
  ..
}
 
typedef boost::shared_ptr<Option> option_ptr;   // This typedef stops excessive C++ syntax later
 
void vec_option(std::vector<option_ptr>& vec)
{
  option_ptr ptr_opt(new Option());  // Separate allocation to avoid problems if exceptions are thrown
  vec.push_back(ptr_opt);
  ..
  vec.pop_back();
}

NOTE: So why does this work?

  • Shared pointers make use of reference counting, which ensures that the allocation option object (ptr_opt) is correctly transferred into the vector, in this line: vec.push_back(ptr_opt);.
  • Although it is not absolutely necessary to use a shared_ptr, doing so here is because debugging dangling pointers when using different types is extremely painful.
  • It is much easier to optimise working code than trying to prematurely optimise non-working code!

C++11 Smart Pointers and STL Containers

In modern C++, which utilises the C++11 standard, the use of auto_ptrs has become deprecated.

#include <vector>
#include <memory>  // std::shared_ptr is included in the "memory" header
 
class Option {
public:
  ..
}
 
typedef std::shared_ptr<Option> option_ptr;   // This typedef stops excessive C++ syntax later
 
void vec_option(std::vector<option_ptr>& vec)
{
  option_ptr ptr_opt(new Option());  // Separate allocation to avoid problems if exceptions are thrown
  vec.push_back(ptr_opt);
  ..
  vec.pop_back();
}

NOTE: Using shared pointers can be considered bad practice, as it means that the programmer has not given sufficient thought to the lifetime of the object or where it should actually be deleted.

  • Another suggestion is to use std::unique_ptr for situations where the container object “owns” the elements within it, rather than the elements having a distinct external lifetime of their own.