Using weak_ptr

The weak_ptr holds a weakly referenced pointer to an object that is managed by a shared_ptr (or by multiple shared_ptr instances). The weak_ptr does not affect the strong ref count. You typically construct a weak_ptr out of a shared_ptr, and then when you need to access the underlying object, you call lock() on the weak_ptr which gives you a shared_ptr (with ref count incremented). One use of weak_ptr types is to help avoid circular references, which often leads to memory leaks as objects continue to remain in memory. Another example would be cached access to objects that may or may not be alive in memory. So you’d store the weak_ptrs and whenever you need to access the object, you’d check to see if the object’s alive, and create a shared_ptr from the weak_ptr as needed. The code snippet below shows such a pattern.

class NumberStoreCache
{
private:
  unordered_map<int, weak_ptr<NumberStore>> cache;

  shared_ptr<NumberStore> AddToCache(int number)
  {
    shared_ptr<NumberStore> store = make_shared<NumberStore>(
        number);
    weak_ptr<NumberStore> weak(store);
    cache[number] = weak;
    return store;
  }

public:
  shared_ptr<NumberStore> GetNumberStore(int number)
  {
    if (cache.find(number) == cache.end())
    {
      return AddToCache(number); // call 1 
    }
    else
    {
      weak_ptr<NumberStore> weak = cache[number];
      if (weak.expired())
      {
        return AddToCache(number); // call 2
      }
      else
      {
        return weak.lock(); // call3
      }
    }
  }
};

void Foo()
{
  NumberStoreCache nsCache;
  auto ns1 = nsCache.GetNumberStore(10); // call 1
  ns1.reset();
  auto ns2 = nsCache.GetNumberStore(10); // call 2
  auto ns3 = nsCache.GetNumberStore(10); // call 3
}

Using shared_ptr

While unique_ptr is meant for single-owner scenarios, shared_ptr is the reference counted smart pointer class that allows you to share the smart pointer around your code. Consider the code snippet below, which uses the NumberStore example from the previous blog entry.

void SharedPtrVersion()
{
  shared_ptr<NumberStore> number(
      new NumberStore(100)); // Ref Count : 1
  Foo(number); // Ref Count : 1 on function return
  shared_ptr<NumberStore> copy = number; // Ref Count : 2
  Foo(number); // Ref Count : 2 on function return
  copy.reset(); // Ref Count : 1
  Foo(number); // Ref Count : 1 on function return
  Foo(copy); // This will crash
}

The output will be:

NumberStore ctor
Foo : 100 Ref Count : 2
Foo : 100 Ref Count : 3
Foo : 100 Ref Count : 2
NumberStore dtor

The ref count goes up inside Foo‘s body as Foo has received a copy of the shared_ptr. It goes back as soon as Foo returns. Creating a copy increments the ref count as expected. Calling reset() decrements the ref count and releases ownership. Trying to pass around a reset shared_ptr will give you a crash / access violation.

The shared_ptr class also allows you to pass a lambda as the deletion method called in the destructor, so you can do something like this.

void SharedPtrArrayVersion()
{
  shared_ptr<NumberStore> number(
    new NumberStore[3], 
    [](NumberStore* pNumStore) { delete[] pNumStore; });
}

Note: when you call reset, if the ref count drops to 0, the object is destroyed. Example:

void SharedPtrVersion2()
{
  shared_ptr<NumberStore> number(new NumberStore(100));
  shared_ptr<NumberStore> copy = number;
  number.reset();
  copy.reset(); // destructor is called here
  cout << "end of method" << endl;
}

The next blog entry will talk about the weak_ptr class (the last of the 3 smart pointers introduced in C++ 11).