The Boost C++ Libraries has been updated. The second edition was published in September 2014 and introduces 72 Boost libraries with more than 430 examples. It is available at Amazon, Barnes and Noble, for Kindle, as an Epub and as a PDF file. The second edition is based on C++11 and the Boost libraries 1.55.0 and 1.56.0 with the latter version having been released in August 2014.

Find the second edition online at http://theboostcpplibraries.com/

The Boost C++ Libraries


Chapter 2: Smart Pointers


Table of Contents

This book is licensed under a Creative Commons License.


2.1 General

The first version of the C++ standard adapted in 1998 only provides one smart pointer: std::auto_ptr. In principle, it behaves like a regular pointer: It provides access to a dynamically allocated object by storing its address. However, std::auto_ptr is considered intelligent and smart since it automatically releases the contained object during destruction by calling the delete operator. This certainly requires that it is provided with the address of the object - returned by the new operator - at initialization. Since the delete operator is called within the destructor of std::auto_ptr, the associated memory for the contained object is guaranteed to be released. This is one advantage of smart pointers.

This becomes even more important in conjunction with exceptions: Without smart pointers such as std::auto_ptr, every function allocating dynamic memory would be required to catch every possible exception in order to release the memory prior to passing on the exception to its calling function. The Boost C++ Library Smart Pointers provides many additional smart pointers that can be used in all kind of situations.


2.2 RAII

The principle of smart pointers is based on a common idiom named RAII: Resource acquisition is initialization. Smart pointers are only one example for this idiom - certainly a very prominent one. Smart pointers are used to ensure that dynamically allocated memory is released properly under all circumstances, freeing the developer from the burden of managing this on her own. This includes scenarios in which execution of a function is interrupted by an exception and the instruction to release the memory is skipped. This guarantee is accomplished by initializing a smart pointer with the address of a dynamically allocated object which in turn is used to release the memory during destruction. Since the destructor is always executed the contained memory is therefore always released.

RAII is applied whenever a second instruction is mandatory in order to release a resource previously allocated by another instruction. Since many C++ applications require dynamically managed memory, smart pointers are an important RAII class. RAII itself can be applied in many other scenarios though.

#include <windows.h> 

class windows_handle 
{ 
  public: 
    windows_handle(HANDLE h) 
      : handle_(h) 
    { 
    } 

    ~windows_handle() 
    { 
      CloseHandle(handle_); 
    } 

    HANDLE handle() const 
    { 
      return handle_; 
    } 

  private: 
    HANDLE handle_; 
}; 

int main() 
{ 
  windows_handle h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId())); 
  SetPriorityClass(h.handle(), HIGH_PRIORITY_CLASS); 
} 

The above example defines a class named windows_handle that calls the function CloseHandle() in its destructor. Since it is a Windows API function, the program can only be executed on Windows. In Windows, many resources are required to be opened prior to their usage. This implicitly implies that resources should be closed once no longer used. The class windows_handle provides a mechanism to ensure just that.

An instance of type windows_handle is initialized with a handle. Windows utilizes handles in order to uniquely identify its resources. For example, the function OpenProcess() returns a handle of type HANDLE that can be used to access a currently running process. In the code example, the own process is accessed - in other words the application itself.

Using the returned handle, the process priority is increased allowing the application to request more CPU time from the scheduler. This is only for illustration purposes and does not serve any real benefit though. The important point here is that the resource opened with OpenProcess() does not need to be explicitly closed using CloseHandle(). Certainly, the resource is likely to be closed once the application terminates. However, in more complex applications the class windows_handle ensures that a resource is correctly closed if no longer needed. Once a particular resource leaves its visibility scope - in the above example of h at the end of the function main() - the destructor is automatically invoked which in turn closes the contained resource.


2.3 Scoped Pointer

A scoped pointer is a pointer that is the sole owner of a dynamically allocated object. The corresponding class is named boost::scoped_ptr and is defined in boost/scoped_ptr.hpp. Unlike std::auto_ptr, a scoped pointer is not able to transfer ownership of its contained object to another scoped pointer. Once initialized with an address, the dynamically allocated object is released during destruction.

Since a scoped pointer simply stores and solely owns an address, the implementation of boost::scoped_ptr is less complex than std::auto_ptr. boost::scoped_ptr should be preferred if transfer of ownership is not required. In these situations, it may be a better choice than std::auto_ptr to avoid inadvertent transfer of ownership.

#include <boost/scoped_ptr.hpp> 

int main() 
{ 
  boost::scoped_ptr<int> i(new int); 
  *i = 1; 
  *i.get() = 2; 
  i.reset(new int); 
} 

Once initialized, the contained object of the smart pointer boost::scoped_ptr can be accessed through an interface similar to an ordinary pointer. This is achieved by providing the corresponding operator overloads operator*(), operator->() as well as operator bool(). In addition, the methods get() and reset() are available. While the former one returns the address of the contained object, the latter one allows to reinitialize the smart pointer with a new object. In this case, the contained object is automatically destroyed before the new object is assigned.

The contained object is released using the delete operator within the destructor of boost::scoped_ptr. This puts an important restriction on the types of objects boost::scoped_ptr can contain. boost:scoped_ptr is not allowed to be initialized with the address of a dynamically allocated array since this would require a call to the delete[] operator instead. In these cases, the class boost:scoped_array can be used which is introduced next.


2.4 Scoped Array

A scoped array is used just like a scoped pointer. The crucial difference is that the destructor of the scoped array uses the delete[] operator to release the contained object. Since this operator only applies to array objects, a scoped array must be initialized with the address of a dynamically allocated array.

The corresponding class for scoped arrays is boost::scoped_array and is defined in boost/scoped_array.hpp.

#include <boost/scoped_array.hpp> 

int main() 
{ 
  boost::scoped_array<int> i(new int[2]); 
  *i.get() = 1; 
  i[1] = 2; 
  i.reset(new int[3]); 
} 

The class boost::scoped_array provides overloads for the operator[]() and operator bool() operators. Using operator[](), a specific element of the array can be accessed - an object of type boost::scoped_array thus behaves exactly like the array it contains.

Just like boost::scoped_ptr, the methods get() and reset() are provided as well allowing to retrieve and reinitialize the address of the contained object.


2.5 Shared Pointer

This smart pointer is one of the most utilized and has been badly missed in the first version of the C++ standard. It has been added to the standard as part of the Technical Report 1 (TR1). If this report is supported by the development environment used, the class std::shared_ptr defined in memory can be used. Within the Boost C++ library, this smart pointer is named boost::shared_ptr and is defined in boost/shared_ptr.hpp.

The smart pointer boost::shared_ptr is essentially similar to boost::scoped_ptr. The key difference is that boost::shared_ptr is not necessarily the exclusive owner of an object. The ownership can be shared with other smart pointers of type boost::shared_ptr. In these cases, the shared object is not released until the last shared pointer referencing the object is destroyed.

Since ownership can be shared with boost::shared_ptr, copies of any shared pointer can be created - opposed to boost::scoped_ptr. This actually allows the usage of smart pointers with the standard containers - something that cannot be done with std::auto_ptr since it transfers its ownership when copied.

#include <boost/shared_ptr.hpp> 
#include <vector> 

int main() 
{ 
  std::vector<boost::shared_ptr<int> > v; 
  v.push_back(boost::shared_ptr<int>(new int(1))); 
  v.push_back(boost::shared_ptr<int>(new int(2))); 
} 

Thanks to boost::shared_ptr, it is possible to safely use dynamically allocated objects with the standard containers as shown in the above example. Since boost::shared_ptr can share the ownership of its contained object, the copies stored by the container (as well as the additional copies in case the container needs to be rearranged) are equal. As outlined before, this is not the case with std::auto_ptr which therefore should never be stored within a container.

Similar to boost::scoped_ptr, the class boost::shared_ptr provides overloads for the following operators: operator*(), operator->() and operator bool(). In addition, get() and reset() are available as well to retrieve and reinitialize the address of the contained object.

#include <boost/shared_ptr.hpp> 

int main() 
{ 
  boost::shared_ptr<int> i1(new int(1)); 
  boost::shared_ptr<int> i2(i1); 
  i1.reset(new int(2)); 
} 

The example defines two shared pointer i1 and i2 which both refer to the same object of type int. While i1 is explicitly initialized with the address returned by the new operator, i2 is copy-constructed of i1. The address of the contained integer of i1 is then reinitialized by a call to reset(). The previously contained object however is not released since it is still referenced by i2. The smart pointer boost::shared_ptr actually counts the number of shared pointers currently referencing the same object and releases it only once the last shared pointer loses its scope.

By default, boost::shared_ptr uses the delete operator to destroy the contained object. However, the method of destruction can actually be specified as the following example illustrates:

#include <boost/shared_ptr.hpp> 
#include <windows.h> 

int main() 
{ 
  boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()), CloseHandle); 
  SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS); 
} 

The constructor of boost::shared_ptr takes a regular function or a function object as the second parameter which in turn is used for destruction of the contained object. In the given example, the Windows API function CloseHandle() is passed. Once the variable h loses its scope, the passed function is called instead of the delete operator. In order to avoid compiler errors, the function is required to take one parameter of type HANDLE which is the case with CloseHandle().

This example actually behaves the same as the one illustrating the RAII idiom earlier in this chapter. However, instead of defining a separate class windows_handle, the example takes advantage of the specific characteristics of boost::shared_ptr by passing a method to the constructor which is automatically called once the shared pointer loses its scope.


2.6 Shared Array

A shared array works essentially the same as a shared pointer. The crucial difference is that the destructor of the shared array uses the delete[] operator by default to release the contained object. Since this operator only applies to array objects, a shared array must be initialized with the address of a dynamically allocated array.

The corresponding class for shared arrays is boost::shared_array and is defined in boost/shared_array.hpp.

#include <boost/shared_array.hpp> 
#include <iostream> 

int main() 
{ 
  boost::shared_array<int> i1(new int[2]); 
  boost::shared_array<int> i2(i1); 
  i1[0] = 1; 
  std::cout << i2[0] << std::endl; 
} 

Just like with a shared pointer, the ownership of the contained object can be shared with other shared arrays. The example defines two variables i1 and i2 which both refer to the same dynamically allocated array. The value 1 - stored using the operator[]() of i1 - can be referenced and e.g. printed to the standard output stream using i2.

As with all the smart pointers introduced in this chapter, boost::shared_array also provides the methods get() and reset(). In addition, the operator bool() is overloaded as well.


2.7 Weak Pointer

Every smart pointer introduced so far can be used individually for different scenarios. In contrast, a weak pointer only makes sense if used in conjunction with a shared pointer. It is defined as boost::weak_ptr in boost/weak_ptr.hpp

#include <windows.h> 
#include <boost/shared_ptr.hpp> 
#include <boost/weak_ptr.hpp> 
#include <iostream> 

DWORD WINAPI reset(LPVOID p) 
{ 
  boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); 
  sh->reset(); 
  return 0; 
} 

DWORD WINAPI print(LPVOID p) 
{ 
  boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); 
  boost::shared_ptr<int> sh = w->lock(); 
  if (sh) 
    std::cout << *sh << std::endl; 
  return 0; 
} 

int main() 
{ 
  boost::shared_ptr<int> sh(new int(99)); 
  boost::weak_ptr<int> w(sh); 
  HANDLE threads[2]; 
  threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); 
  threads[1] = CreateThread(0, 0, print, &w, 0, 0); 
  WaitForMultipleObjects(2, threads, TRUE, INFINITE); 
} 

A boost::weak_ptr must always be initialized with a boost::shared_ptr. Once initialized, it basically offers only one useful method: lock(). It returns a boost::shared_ptr that shares ownership with the shared pointer used to initialize the weak pointer. In case the shared pointer does not contain any object, the returned one is empty as well.

Weak pointers make sense whenever a function is expected to work with an object managed by a shared pointer, but the lifetime of the object does not depend on the function itself. The function should only work with the object as long as it is owned by at least one shared pointer within the program. In case the shared pointer is reset, the object should not be kept alive due to an additional shared pointer inside the corresponding function.

The above example creates two threads inside main() using functions provided by the Windows API. Thus, the example only compiles and executes under the Windows platform.

The first thread executes the function reset() that receives the address of a shared pointer. The second thread executes the function print() which contains the address of a weak pointer. This weak pointer has been previously initialized with the shared pointer.

Once the application is launched, both reset() and print() are executed at the same time. However, the order of execution cannot be predicted. This leads to the potential issue of reset() destroying the object while it is being accessed by print() at the same time.

The weak pointer solves this issue as follows: Invoking lock() returns a shared pointer which points to a valid object in case it exists at the time of the call. If not, the shared pointer is set to 0 and thus equivalent to a standard null pointer.

The weak pointer itself does not have any impact on the lifetime of an object. In order to safely access the object within the print() function nonetheless, lock() returns a shared pointer. This guarantees that - even if the object is attempted to be released by a different thread - it continues to exist thanks to the returned shared pointer.


2.8 Intrusive Pointer

In general, the intrusive pointer works exactly as the shared pointer. However, while boost::shared_ptr internally keeps track of the number of shared pointers referencing a particular object, the developer has to keep track of this information for the intrusive pointer. This is particularly helpful for e.g. framework objects that already keep track of the number of times they are referenced themselves.

The intrusive pointer is defined as boost::intrusive_ptr in boost/intrusive_ptr.hpp.

#include <boost/intrusive_ptr.hpp> 
#include <atlbase.h> 
#include <iostream> 

void intrusive_ptr_add_ref(IDispatch *p) 
{ 
  p->AddRef(); 
} 

void intrusive_ptr_release(IDispatch *p) 
{ 
  p->Release(); 
} 

void check_windows_folder() 
{ 
  CLSID clsid; 
  CLSIDFromProgID(CComBSTR("Scripting.FileSystemObject"), &clsid); 
  void *p; 
  CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), &p); 
  boost::intrusive_ptr<IDispatch> disp(static_cast<IDispatch*>(p)); 
  CComDispatchDriver dd(disp.get()); 
  CComVariant arg("C:\\Windows"); 
  CComVariant ret(false); 
  dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret); 
  std::cout << (ret.boolVal != 0) << std::endl; 
} 

void main() 
{ 
  CoInitialize(0); 
  check_windows_folder(); 
  CoUninitialize(); 
} 

The above example uses functions provided by COM (Component Object Model) thus, only builds and executes under the Windows platform. COM objects are a perfect example for boost::intrusive_ptr since they keep track of the number of pointers referencing them. The internal reference count can be incremented or decremented by 1 using the AddRef() or Release() method respectively. Once the counter reaches 0, the COM object is automatically destroyed.

The two methods AddRef() and Release() are called from within the intrusive_ptr_add_ref() and intrusive_ptr_release() functions to increment and decrement the internal reference counter of the corresponding COM object. The COM object used in this example is named 'FileSystemObject' and is available in Windows by default. It allows access to the underlying file system to e.g. verify whether or not a given directory exists. In the above example, the existence of a directory named C:\Windows is checked. How that works internally depends solely on COM and is irrelevant in regards to the functionality of boost::intrusive_ptr. The crucial point is that once the intrusive pointer disp loses its scope at the end of the check_windows_folder() function, the function intrusive_ptr_release() is going to be automatically called. This in turn will decrement the internal reference counter of the COM object 'FileSystemObject' to 0 and thus destroy the object.


2.9 Pointer Container

After meeting the different smart pointers of the Boost C++ Libraries, one should be able to write safe code for dynamically allocated objects as well as arrays. Many times, these objects are required to be stored within a container which - as seen above - is fairly easy using boost::shared_ptr and boost::shared_array.

#include <boost/shared_ptr.hpp> 
#include <vector> 

int main() 
{ 
  std::vector<boost::shared_ptr<int> > v; 
  v.push_back(boost::shared_ptr<int>(new int(1))); 
  v.push_back(boost::shared_ptr<int>(new int(2))); 
} 

While the code in the above example is absolutely correct and smart pointers can be used the given way, it is actually impractical for a couple of reasons. For once, the repetitive declaration of boost::shared_ptr requires more typing. In addition, copying the boost::shared_ptr to, from or within the container requires incrementing and decrementing the internal reference count constantly and thus is deemed very inefficient. For these reasons, the Boost C++ Libraries provide Pointer Container that are specialized for the management of dynamically allocated objects.

#include <boost/ptr_container/ptr_vector.hpp> 

int main() 
{ 
  boost::ptr_vector<int> v; 
  v.push_back(new int(1)); 
  v.push_back(new int(2)); 
} 

The class boost::ptr_vector defined in boost/ptr_container/ptr_vector.hpp works the same way as the container used and initialized with the boost::shared_ptr template parameter in the previous example. Since boost::ptr_vector is specialized for dynamically allocated objects, it is easier and more efficient to use though. However, as boost::ptr_vector is the solely owner of all contained objects, the ownership cannot be shared with a shared pointer not stored inside the container opposed to using std::vector<boost::shared_ptr<int> >.

Besides boost::ptr_vector, additional containers specialized for managing dynamically allocated objects are available including boost::ptr_deque, boost::ptr_list, boost::ptr_set, boost::ptr_map, boost::ptr_unordered_set and boost::ptr_unordered_map. These containers are equivalent to the ones provided by the C++ standard. The last two containers match the std::unordered_set and std::unordered_map containers added to the C++ standard as part of the Technical Report 1. They are also implemented as boost::unordered_set and boost::unordered_map by the Boost C++ Libraries that can be utilized if the used implementation of the C++ Standard does not support the Technical Report 1.


2.10 Exercises

You can buy solutions to all exercises in this book as a ZIP file.

  1. Optimize the following program by using an appropriate smart pointer:

    #include <iostream> 
    #include <cstring> 
    
    char *get(const char *s) 
    { 
      int size = std::strlen(s); 
      char *text = new char[size + 1]; 
      std::strncpy(text, s, size + 1); 
      return text; 
    } 
    
    void print(char *text) 
    { 
      std::cout << text << std::endl; 
    } 
    
    int main(int argc, char *argv[]) 
    { 
      if (argc < 2) 
      { 
        std::cerr << argv[0] << " <data>" << std::endl; 
        return 1; 
      } 
    
      char *text = get(argv[1]); 
      print(text); 
      delete[] text; 
    } 
  2. Optimize the following program:

    #include <vector> 
    
    template <typename T> 
    T *create() 
    { 
      return new T; 
    } 
    
    int main() 
    { 
      std::vector<int*> v; 
      v.push_back(create<int>()); 
    }