Overview of smart pointers

Peter Edwards, Arista Networks
  • Concepts
  • As applied in std::shared_ptr et al

Basics

 

auto foo = new Foo();
// ... stuff
delete foo
                         

TL;DR: "raw" C++ memory management is hard.

We can use RAII to create "scoped" pointers

 

Only allows one managed "reference" to object

 
 

std::auto_ptr

std::auto_ptr had weird copy semantics

 

... Compiles, but....

 

std::unique_ptr

 

unique_ptr explicitly prevents you doing something like that...

 

Don't use auto-ptr, it's broken

unique_ptr is more useful, but still has obvious restrictions

Intrusive Reference counting

  • put a reference count in every object
  • increment refcount for each reference we create
  • decrement refcount for each reference we destroy
  • delete object when refcount hits 0
 

Intrusive Reference Counting - ctd

 
 

Intrusive Reference Counting - Summary

  • This is actually a workable system, if you can deal with the "intrusive" bit
  • boost has boost::intrusive_ptr
  • The Python interpreter uses this, along with mark/sweep style GC. (see PyObject's ob_refcnt)
  • ... lots of examples... linux kernel, C++ frameworks, etc
  • this is also exactly how TACC refcounting works.
    • all tacc objects that have refcounts inherit from VFPtrInterface, which contains a union consisting of atomicRefAndFlags_ or nonAtomicRefAndFlags_
    • Tac::Ptr is essentially similar to our type above.

Non-intrusive Reference Counting

  • put reference count in a separate object to the resource-tracked object
  • (almost) any type can therefore be managed by such a smart pointer
sharedptr diagram

Non-intrusive Reference Counting - ctd

Non-intrusive Reference Counting - Summary

  • nominally requires extra heap allocation
  • more expensive to pass around (two pointers instead of one)
  • If you try, you can create unrelated control blocks for the same pointer...

std::shared_ptr

Our SharedPtr is obviously a simplification, but it is conceptually similar to how std::shared_ptr works:

sharedptr picture

We can see the memory allocation for ourselves...

We can use std::make_shared to improve matters

Using make_shared, we get this:

makeshared picture

shared_from_this

What happens if a member function needs to pass "this" as a shared_ptr to some function/method?

note this requires that the type already has a shared pointer referring to it. (Our second call to Foo::m is invalid)

 
 

std::weak_ptr

  • Like a "raw" pointer, but "safe". Object can be deleted while the weak pointer exists
  • Try to convert to a shared_ptr - works if and only if the object has not yet been deleted.
  • Can be used to prevent reference loops, etc.

How does weak_ptr keep track of things that have been deleted?

weakptr details

The control block holds a count of weak references as well as "actual" references

There's one extra weak reference for all the shared references.

weakptr cleanup details

When all the shared/strong references are gone away, we can delete the shared object and reclaim its memory

When all the weak references are gone away, we can eventually delete the control block

Beware make_shared and std::weak_ptr!

How might enable_shared_from_this be implemented?

Some gory details

Thread Safety and shared/weak ptrs

  • Modifications to reference counts in the control block are atomic.
  • Multiple threads can, for example, safely copy the same shared_ptr simultaneously
  • The shared_ptr's themselves, however, are not thread-safe: two threads cannot simultaneously modify the same shared_ptr instance

Deleting objects

Confused?

Deleting objects

For shared ptrs, it makes sense if things are always destroyed the same way, regardless of who holds the last reference

Deleting objects

We implement this by having a virtual function to do the work of destroying the object in the control block

We now have a reasonably complete picture of the content of std::shared_ptr

sharedptr whatgetsdeleted

Why use a distinct pointer in the control block here?

  • Mulitple inheritance, and the actual value of "this"
  • make_shared
  • Custom deleters
  • Aliasing constructor

aliasing constructor


namespace std {
   template <typename T> class shared_ptr {
      ...
      template <class Y>
      shared_ptr (const shared_ptr<Y>& r, T* p)
                        noexcept;
      ...
   };
}
                           
sharedptr whatgetsdeleted

aliasing constructor

Questions?

Slides live at https://peadar.github.io/smartptrs

... and the source is here