The Boost C++ Libraries

Shared Ownership

The smart pointer boost::shared_ptr is similar to boost::scoped_ptr. The key difference is that boost::shared_ptr is not necessarily the exclusive owner of an object. Ownership can be shared with other smart pointers of type boost::shared_ptr. In such a case, the shared object is not released until the last copy of the shared pointer referencing the object is destroyed. Because boost::shared_ptr can share ownership, the smart pointer can be copied, which isn’t possible with boost::scoped_ptr.

boost::shared_ptr is defined in the header file boost/shared_ptr.hpp.

Example 1.3. Using boost::shared_ptr
#include <boost/shared_ptr.hpp>
#include <iostream>

int main()
{
  boost::shared_ptr<int> p1{new int{1}};
  std::cout << *p1 << '\n';
  boost::shared_ptr<int> p2{p1};
  p1.reset(new int{2});
  std::cout << *p1.get() << '\n';
  p1.reset();
  std::cout << std::boolalpha << static_cast<bool>(p2) << '\n';
}

Example 1.3 uses two smart pointers, p1 and p2, of the type boost::shared_ptr. p2 is initialized with p1 which means both smart pointers share ownership of the same int object. When reset() is called on p1, a new int object is anchored in p1. This doesn’t mean that the existing int object is destroyed. Since it is also anchored in p2, it continues to exist. After the call to reset(), p1 is the sole owner of the int object with the number 2 and p2 the sole owner of the int object with the number 1.

boost::shared_ptr uses a reference counter internally. Only when boost::shared_ptr detects that the last copy of the smart pointer has been destroyed is the contained object released with delete.

Like boost::scoped_ptr, boost::shared_ptr overloads operator bool(), operator*(), and operator->(). The member functions get() and reset() are provided to retrieve the currently stored address or store a new one.

As a second parameter, a deleter can be passed to the constructor of boost::shared_ptr. The deleter must be a function or function object that accepts as its sole parameter a pointer of the type boost::shared_ptr was instantiated with. The deleter is called in the destructor instead of delete. This makes it possible to manage resources other than dynamically allocated objects in a boost::shared_ptr.

Example 1.4. boost::shared_ptr with a user-defined deleter
#include <boost/shared_ptr.hpp>
#include <Windows.h>

int main()
{
  boost::shared_ptr<void> handle(OpenProcess(PROCESS_SET_INFORMATION, FALSE,
    GetCurrentProcessId()), CloseHandle);
}

In Example 1.4 boost::shared_ptr is instantiated with void. The first parameter passed to the constructor is the return value from OpenProcess(). OpenProcess() is a Windows function to get a handle to a process. In the example, OpenProcess() returns a handle to the current process – to the example itself.

Windows uses handles to refer to resources. Once a resource isn’t used anymore, the handle must be closed with CloseHandle(). The only parameter expected by CloseHandle() is the handle to close. In the example, CloseHandle() is passed as a second parameter to the constructor of boost::shared_ptr. CloseHandle() is the deleter for handle. When handle is destroyed at the end of main(), the destructor calls CloseHandle() to close the handle that was passed as a first parameter to the constructor.

Note

Example 1.4 only works because a Windows handle is defined as void*. If OpenProcess() didn’t return a value of type void* and if CloseHandle() didn’t expect a parameter of type void*, it wouldn’t be possible to use boost::shared_ptr in this example. The deleter does not make boost::shared_ptr a silver bullet to manage arbitrary resources.

Example 1.5. Using boost::make_shared
#include <boost/make_shared.hpp>
#include <typeinfo>
#include <iostream>

int main()
{
  auto p1 = boost::make_shared<int>(1);
  std::cout << typeid(p1).name() << '\n';
  auto p2 = boost::make_shared<int[]>(10);
  std::cout << typeid(p2).name() << '\n';
}

Boost.SmartPointers provides a helper function boost::make_shared() in boost/make_shared.hpp. With boost::make_shared() you can create a smart pointer of type boost::shared_ptr without having to calling the constructor of boost::shared_ptr yourself.

The advantage of boost::make_shared() is that the memory for the object that has to be allocated dynamically and the memory for the reference counter used by the smart pointer internally can be reserved in one chunk. Using boost::make_shared() is more efficient than calling new to create a dynamically allocated object and calling new again in the constructor of boost::shared_ptr to allocate memory for the reference counter.

You can use boost::make_shared() for arrays, too. With the second call to boost::make_shared() in Example 1.5, an int array with ten elements is anchored in p2.

boost::shared_ptr has only supported arrays since Boost 1.53.0. boost::shared_array provides a smart pointer that is analogous to boost::shared_ptr in the same way that boost::scoped_array is analogous to boost::scoped_ptr. When built with Visual C++ 2013 and Boost 1.53.0 or newer, Example 1.5 prints class boost::shared_ptr<int [0]> for p2.

Since Boost 1.53.0, boost::shared_ptr supports single objects and arrays and detects whether it has to release resources with delete or delete[]. Because boost::shared_ptr also overloads operator[] (since Boost 1.53.0), this smart pointer is an alternative for boost::shared_array.

Example 1.6. Using boost::shared_array
#include <boost/shared_array.hpp>
#include <iostream>

int main()
{
  boost::shared_array<int> p1{new int[1]};
  {
    boost::shared_array<int> p2{p1};
    p2[0] = 1;
  }
  std::cout << p1[0] << '\n';
}

boost::shared_array complements boost::shared_ptr: Since boost::shared_array calls delete[] in the destructor, this smart pointer can be used for arrays. For versions older than Boost 1.53.0, boost::shared_array had to be used for arrays because boost::shared_ptr didn’t support arrays.

boost::shared_array is defined in boost/shared_array.hpp.

In Example 1.6, the smart pointers p1 and p2 share ownership of the dynamically allocated int array. When the array in p2 is accessed with operator[] to store the number 1, the same array is accessed with p1. Thus, the example writes 1 to standard output.

Like boost::shared_ptr, boost::shared_array uses a reference counter. The dynamically allocated array is not released when p2 is destroyed because p1 still contains a reference to that array. The array is only destroyed at the end of main() when the scope ends for p1.

boost::shared_array also provides the member functions get() and reset(). Furthermore, it overloads the operator operator bool.

Example 1.7. boost::shared_ptr with BOOST_SP_USE_QUICK_ALLOCATOR
#define BOOST_SP_USE_QUICK_ALLOCATOR
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <ctime>

int main()
{
  boost::shared_ptr<int> p;
  std::time_t then = std::time(nullptr);
  for (int i = 0; i < 1000000; ++i)
    p.reset(new int{i});
  std::time_t now = std::time(nullptr);
  std::cout << now - then << '\n';
}

It can make sense to prefer a smart pointer like boost::shared_ptr over the ones from the standard library. Boost.SmartPointers supports macros to optimize the behavior of the smart pointers. Example 1.7 uses the macro BOOST_SP_USE_QUICK_ALLOCATOR to activate an allocator shipped with Boost.SmartPointers. This allocator manages memory chunks to reduce the number of calls to new and delete for reference counters. The example calls std::time() to measure the time before and after the loop. While the time it takes to execute the loop depends on the computer, the example may run faster with BOOST_SP_USE_QUICK_ALLOCATOR than without. The documentation for Boost.SmartPointers doesn’t mention BOOST_SP_USE_QUICK_ALLOCATOR. Thus, you should profile your program and compare the results you get with and without BOOST_SP_USE_QUICK_ALLOCATOR.

Tip

In addition to BOOST_SP_USE_QUICK_ALLOCATOR, Boost.SmartPointers supports macros like BOOST_SP_ENABLE_DEBUG_HOOKS. The names of the macros start with BOOST_SP_ which makes it easy to search for them in the header files to get an overview on the available macros.