The Boost C++ Libraries

Special Smart Pointers

Every smart pointer introduced so far can be used individually in different scenarios. However, boost::weak_ptr only makes sense if used in conjunction with boost::shared_ptr. boost::weak_ptr is defined in boost/weak_ptr.hpp.

Example 1.8. Using boost::weak_ptr
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <thread>
#include <functional>
#include <iostream>

void reset(boost::shared_ptr<int> &sh)
{
  sh.reset();
}

void print(boost::weak_ptr<int> &w)
{
  boost::shared_ptr<int> sh = w.lock();
  if (sh)
    std::cout << *sh << '\n';
}

int main()
{
  boost::shared_ptr<int> sh{new int{99}};
  boost::weak_ptr<int> w{sh};
  std::thread t1{reset, std::ref(sh)};
  std::thread t2{print, std::ref(w)};
  t1.join();
  t2.join();
}

boost::weak_ptr must be initialized with a boost::shared_ptr. Its most important member function is lock(). lock() returns a boost::shared_ptr that shares ownership with the shared pointer used to initialize the weak pointer. In case the shared pointer is empty, the returned pointer will be empty as well.

boost::weak_ptr makes 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 can only use the object as long as it is owned by at least one shared pointer somewhere else in the program. In case the shared pointer is reset, the object cannot be kept alive because of an additional shared pointer inside the corresponding function.

Example 1.8 creates two threads in main(). The first thread executes the function reset(), which receives a reference to a shared pointer. The second thread executes the function print(), which receives a reference to a weak pointer. This weak pointer has been previously initialized with the shared pointer.

Once the program 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 problem of reset() destroying the object while it is being accessed by print().

The weak pointer solves this issue as follows: invoking lock() returns a shared pointer that points to a valid object if one exists at the time of the call. If not, the shared pointer is set to 0 and is equivalent to a null pointer.

boost::weak_ptr itself does not have any impact on the lifetime of an object. To safely access the object within the print() function, lock() returns a boost::shared_ptr. This guarantees that even if a different thread attempts to release the object, it will continue to exist thanks to the returned shared pointer.

Example 1.9. Using boost::intrusive_ptr
#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), false};
  CComDispatchDriver dd{disp.get()};
  CComVariant arg{"C:\\Windows"};
  CComVariant ret{false};
  dd.Invoke1(CComBSTR{"FolderExists"}, &arg, &ret);
  std::cout << std::boolalpha << (ret.boolVal != 0) << '\n';
}

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

In general, boost::intrusive_ptr works the same as boost::shared_ptr. However, while boost::shared_ptr keeps track of the number of shared pointers referencing a particular object, the developer has to do this when using boost::intrusive_ptr. This can make sense if other classes already keep track of references.

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

Example 1.9 uses functions provided by COM and, thus, can only be built and run on Windows. COM objects are a good example for boost::intrusive_ptr because they track the number of pointers referencing them. The internal reference counter can be incremented or decremented by 1 with the member functions AddRef() and Release(). Once the counter reaches 0, the COM object is automatically destroyed.

The two member functions AddRef() and Release() are called from intrusive_ptr_add_ref() and intrusive_ptr_release(). Boost.Intrusive expects a developer to define these two functions, which are automatically called whenever a reference counter must be incremented or decremented. The parameter passed to these functions is a pointer to the type that was used to instantiate the class template boost::intrusive_ptr.

The COM object used in this example is called FileSystemObject and is available on Windows by default. It provides access to the underlying file system to, for example, check whether a given directory exists. In Example 1.9, the existence of a directory called C:\Windows is checked. How that works internally depends solely on COM and is irrelevant to the functionality of boost::intrusive_ptr. The crucial point is that once the intrusive pointer disp goes out of scope at the end of check_windows_folder(), the function intrusive_ptr_release() is called automatically. This in turn will decrement the internal reference counter of FileSystemObject to 0 and destroy the object.

The parameter false passed to the constructor of boost::intrusive_ptr prevents intrusive_ptr_add_ref() from being called. When a COM object is created with CoCreateInstance(), the counter is already set to 1. Therefore, it must not be incremented with intrusive_ptr_add_ref().