Smart Pointers in C++
Francesco Casalegno
Raw pointers are dangerous!
● In C++ we use pointers to handle memory that was dynamically allocated with new.
However, raw pointers can be dangerous for many reasons.
2
Person* p = new Person("John", 25);
p->call(); // exception may be thrown here...
delete p; // ... and we never get here!
→ Missing delete causes memory leaks.
This easily happens when exceptions are not correctly handled.
→ Calling delete more than once yields undefined behavior.
Person* p = new Person("John", 25);
Person* q = p;
delete p;
delete q; // undefined behavior: object already deleted!
→ After a delete we get a dangling pointer.
Person* p = new Person[10];
delete[] p; // p still points to the same memory location!
p[0]->call(); // undefined behavior!
p = nullptr; // ok, now p is not dangling any more
Smart Pointers
● You can avoid these drawbacks by using smart pointers.
A smart pointers is a wrapper of a raw pointer providing same functionalities with more safety:
→ same syntax as raw pointers for deferencing: *p, p->val, and p[idx]
→ automatic management of dynamically allocated memory lifetime
→ exception-safe destruction
→ automatically set to nullptr, avoiding dangling pointers
● We have 4 kinds of smart pointers, all defined in the header <memory>
→ std::auto_ptr (from C++98) was a first naive attempt to implement a smart pointer with
exclusive-ownership. It is deprecated from C++11, and removed from the STL from C++14.
→ std::unique_ptr (from C++11) is a smart pointer used for exclusive-ownership
that can be copied only with move semantics.
→ std::shared_ptr (from C++11) is a smart pointer used for shared-ownership with automatic
garbage collection based on a reference count.
→ std::weak_ptr (from C++11) is a smart pointer used for observing without owning. It is similar to
std::shared_ptr, but it does not contribute to the reference count. 3
std::auto_ptr
● An auto_ptr provides exclusive ownership for a pointer that it holds. When auto_ptr is destroyed, the
object pointed is also automatically destroyed through a call to delete.
● Introduced pre C++11, so no move semantics: how to copy the pointer while keeping exclusivity?
Workaround: c-tor and assignment receive by value and steal the ownership!
● This (deprecated) smart pointers clearly had many issues:
→ when it gets destroyed it always call delete, so no dynamic arrays (delete[])
→ copy / move c-tor and assignment take arguments by non-const argument (same behavior for
lval or rval) and have strange side-effects (stealing ownership): does not respect the natural
requirements of CopyConstructible and CopyAssignable
→ therefore it cannot be used in STL containers (pre C++11) or it is deprecated to do it (C++11)
... If you have C++11, then just use std::unique_ptr instead!
4
{
std::auto_ptr<Person> p(new Person("John", 25));
p->call(); // if exception is thrown, delete is automatically called
} // here p goes out of scope, delete is automatically called
std::auto_ptr<Person> p("John", 25);
std::auto_ptr<Person> q = p; // copy c-tor: q points to object, p is nullptr!
p = q; // copy assignment: p points to object, q is nullptr!
std::unique_ptr
● A unique_prt u provides exclusive ownership for the pointer p that it holds. When u is destroyed, the
object pointed by p is disposed using p’s associated deleter (custom of default delete/delete[] )
● Main difference with respect to auto_prt:
→ copy c-tor and assignment are deleted, so that copying from lvalue is not allowed
→ move c-tor and assignment are implemented to allow ownership change by natural move semantics
5
std::unique_ptr<Person> p(new Person("John", 25));
std::unique_ptr<Person> q = p; // compiler error! copy c-tor is deleted
std::unique_ptr<Person> q = std::move(p); // ok, move c-tor called
p = q; // compiler error! copy assignment is deleted
p = std::move(q); // ok, move assignment called
● Important features:
→ template specialization unique_prt<T[]> to handle dynamic arrays
→ copy from lvalue is deleted, copy from rvalue is implemented using move semantics
so that it can suitably work in a STL container
→ custom deleter can be provided if needed
→ pretty simple wrapper implementation, so no overhead in memory (unless custom deleter) or runtime
→ easy conversion to shared_ptr
John
Alice
std::unique_ptr – useful methods
● Observes
● Modifiers
6
pointer get() const;
→ returns a pointer to the managed object
deleter& get_deleter();
→ returns a reference to the deleter object used for the disposal of the managed object
pointer release();
→ releases ownership by returning the managed object + setting the internal pointer to nullptr
void reset( pointer ptr = pointer() );
→ replaces the internal pointer with ptr, and disposes of the previously owned object by calling
the deleter
std::shared_ptr
● A shared_ptr provides shared ownership: many pointers may own the same object and the last one
to survive is responsible of its disposal through the deleter.
● This garbage collection mechanism is based on a reference counter contained in a control block.
Each new shared owner copies the pointer to the control block and increases the count by 1.
● Important features:
→ until C++17 no template specialization shared_prt<T[]> to handle dynamic arrays
→ custom deleter and custom allocator (for internal data allocation) can be provided if needed
→ it is CopyConstructible and CopyAssignable, so it can work in a STL container
→ non-trivial implementation and memory overhead (typically 2 pointers: object itself + ctrl block)
→ many operations require atomic (thread safety!) update of ref_counter, which may be slow
→ aliasing c-tor allows to share ownership on an object and point elsewhere (e.g. object’s member)
7
std::shared_ptr p(new Person("John", 25)); // construction: ref_counter=1
std::shared_ptr q = p; // new owner: ref_counter=2
std::shared_ptr r = std::move(p); // r steals ownership: ref_counter=2
p.reset(new Person("Alice", 23)); // p resets managed object: ref_counter=1
r = nullptr; // move assign to nullptr: ref_counter=0,
and deleter is called!
ptr to obj
shared_ptr
“John”
25
ref_counter
custom del
custom alloc
ptr to ctrl
John
object controller
std::shared_ptr – useful methods
● Observes
● Modifiers
8
T* get() const;
→ returns stored pointer
long use_count() const;
→ returns the number of shared pointers managing current object
bool unique() const noexcept;
→ returns whether use_count()==1
template< class Y > void reset( Y* ptr );
→ replaces the managed object with the one pointed by ptr, and disposes of the previously
owned object by calling the deleter
std::weak_ptr
● A weak_ptr tracks without owning a pointer to an object managed by a shared_ptr. It implements weak
ownership: the object needs to be accessed only if still exists, and it may be deleted by others.
If we need to access the object we have to convert it into a shared_ptr.
● Used as observer to determine if the pointer is dangling before converting to shared_ptr and using it.
Typical use cases are implementing a cache and breaking cycles:
Example with cycles: how to do if A and C share ownership on B, but B must also point to A?
→ raw ptr: if A gets destroyed, B has no way to know it and its pointer dangles
→ shared_ptr: if C gets destroyed we still have a cycle and therefore A and B are never deleted!
The only reasonable solution is weak_ptr!
● Important features:
→ cannot directly access the pointed object: *p, p->val, or p[idx] are not implemented!
→ convert to shared_ptr (after check for dangling!) when you need to access object
→ implemented very similar to shared_ptr, but no contribution to ref_counter update
→ same memory overhead as shared_ptr, and performance penalty for accessing due to
conversion
9
A B C
John Alice
std::weak_ptr – useful methods
● Observes
● Modifiers
10
long use_count() const;
→ returns the number of shared pointers managing current object
bool expired() const;
→ check if use_count()==0
std::shared_ptr<T> lock() const;
→ creates a new shared_ptr owning the managed object
template< class Y > void reset();
→ releases the reference to the managed object
Do we still need raw pointers?
● Smart pointers implement automatic management of dynamically allocated memory.
This comes with little overhead, or even no overhead as in the case of unique_ptr.
● However, there are still cases when raw pointers are needed.
→ when the pointer really just mean an address, not ownership of an object
→ to implement iterators
→ to handle an array decaying into a pointer
→ when working with legacy code (e.g. pre-C++11)
→ when passing arguments by address to modify their content
12

smart pointers are unique concept to avoid memory leakage

  • 1.
    Smart Pointers inC++ Francesco Casalegno
  • 2.
    Raw pointers aredangerous! ● In C++ we use pointers to handle memory that was dynamically allocated with new. However, raw pointers can be dangerous for many reasons. 2 Person* p = new Person("John", 25); p->call(); // exception may be thrown here... delete p; // ... and we never get here! → Missing delete causes memory leaks. This easily happens when exceptions are not correctly handled. → Calling delete more than once yields undefined behavior. Person* p = new Person("John", 25); Person* q = p; delete p; delete q; // undefined behavior: object already deleted! → After a delete we get a dangling pointer. Person* p = new Person[10]; delete[] p; // p still points to the same memory location! p[0]->call(); // undefined behavior! p = nullptr; // ok, now p is not dangling any more
  • 3.
    Smart Pointers ● Youcan avoid these drawbacks by using smart pointers. A smart pointers is a wrapper of a raw pointer providing same functionalities with more safety: → same syntax as raw pointers for deferencing: *p, p->val, and p[idx] → automatic management of dynamically allocated memory lifetime → exception-safe destruction → automatically set to nullptr, avoiding dangling pointers ● We have 4 kinds of smart pointers, all defined in the header <memory> → std::auto_ptr (from C++98) was a first naive attempt to implement a smart pointer with exclusive-ownership. It is deprecated from C++11, and removed from the STL from C++14. → std::unique_ptr (from C++11) is a smart pointer used for exclusive-ownership that can be copied only with move semantics. → std::shared_ptr (from C++11) is a smart pointer used for shared-ownership with automatic garbage collection based on a reference count. → std::weak_ptr (from C++11) is a smart pointer used for observing without owning. It is similar to std::shared_ptr, but it does not contribute to the reference count. 3
  • 4.
    std::auto_ptr ● An auto_ptrprovides exclusive ownership for a pointer that it holds. When auto_ptr is destroyed, the object pointed is also automatically destroyed through a call to delete. ● Introduced pre C++11, so no move semantics: how to copy the pointer while keeping exclusivity? Workaround: c-tor and assignment receive by value and steal the ownership! ● This (deprecated) smart pointers clearly had many issues: → when it gets destroyed it always call delete, so no dynamic arrays (delete[]) → copy / move c-tor and assignment take arguments by non-const argument (same behavior for lval or rval) and have strange side-effects (stealing ownership): does not respect the natural requirements of CopyConstructible and CopyAssignable → therefore it cannot be used in STL containers (pre C++11) or it is deprecated to do it (C++11) ... If you have C++11, then just use std::unique_ptr instead! 4 { std::auto_ptr<Person> p(new Person("John", 25)); p->call(); // if exception is thrown, delete is automatically called } // here p goes out of scope, delete is automatically called std::auto_ptr<Person> p("John", 25); std::auto_ptr<Person> q = p; // copy c-tor: q points to object, p is nullptr! p = q; // copy assignment: p points to object, q is nullptr!
  • 5.
    std::unique_ptr ● A unique_prtu provides exclusive ownership for the pointer p that it holds. When u is destroyed, the object pointed by p is disposed using p’s associated deleter (custom of default delete/delete[] ) ● Main difference with respect to auto_prt: → copy c-tor and assignment are deleted, so that copying from lvalue is not allowed → move c-tor and assignment are implemented to allow ownership change by natural move semantics 5 std::unique_ptr<Person> p(new Person("John", 25)); std::unique_ptr<Person> q = p; // compiler error! copy c-tor is deleted std::unique_ptr<Person> q = std::move(p); // ok, move c-tor called p = q; // compiler error! copy assignment is deleted p = std::move(q); // ok, move assignment called ● Important features: → template specialization unique_prt<T[]> to handle dynamic arrays → copy from lvalue is deleted, copy from rvalue is implemented using move semantics so that it can suitably work in a STL container → custom deleter can be provided if needed → pretty simple wrapper implementation, so no overhead in memory (unless custom deleter) or runtime → easy conversion to shared_ptr John Alice
  • 6.
    std::unique_ptr – usefulmethods ● Observes ● Modifiers 6 pointer get() const; → returns a pointer to the managed object deleter& get_deleter(); → returns a reference to the deleter object used for the disposal of the managed object pointer release(); → releases ownership by returning the managed object + setting the internal pointer to nullptr void reset( pointer ptr = pointer() ); → replaces the internal pointer with ptr, and disposes of the previously owned object by calling the deleter
  • 7.
    std::shared_ptr ● A shared_ptrprovides shared ownership: many pointers may own the same object and the last one to survive is responsible of its disposal through the deleter. ● This garbage collection mechanism is based on a reference counter contained in a control block. Each new shared owner copies the pointer to the control block and increases the count by 1. ● Important features: → until C++17 no template specialization shared_prt<T[]> to handle dynamic arrays → custom deleter and custom allocator (for internal data allocation) can be provided if needed → it is CopyConstructible and CopyAssignable, so it can work in a STL container → non-trivial implementation and memory overhead (typically 2 pointers: object itself + ctrl block) → many operations require atomic (thread safety!) update of ref_counter, which may be slow → aliasing c-tor allows to share ownership on an object and point elsewhere (e.g. object’s member) 7 std::shared_ptr p(new Person("John", 25)); // construction: ref_counter=1 std::shared_ptr q = p; // new owner: ref_counter=2 std::shared_ptr r = std::move(p); // r steals ownership: ref_counter=2 p.reset(new Person("Alice", 23)); // p resets managed object: ref_counter=1 r = nullptr; // move assign to nullptr: ref_counter=0, and deleter is called! ptr to obj shared_ptr “John” 25 ref_counter custom del custom alloc ptr to ctrl John object controller
  • 8.
    std::shared_ptr – usefulmethods ● Observes ● Modifiers 8 T* get() const; → returns stored pointer long use_count() const; → returns the number of shared pointers managing current object bool unique() const noexcept; → returns whether use_count()==1 template< class Y > void reset( Y* ptr ); → replaces the managed object with the one pointed by ptr, and disposes of the previously owned object by calling the deleter
  • 9.
    std::weak_ptr ● A weak_ptrtracks without owning a pointer to an object managed by a shared_ptr. It implements weak ownership: the object needs to be accessed only if still exists, and it may be deleted by others. If we need to access the object we have to convert it into a shared_ptr. ● Used as observer to determine if the pointer is dangling before converting to shared_ptr and using it. Typical use cases are implementing a cache and breaking cycles: Example with cycles: how to do if A and C share ownership on B, but B must also point to A? → raw ptr: if A gets destroyed, B has no way to know it and its pointer dangles → shared_ptr: if C gets destroyed we still have a cycle and therefore A and B are never deleted! The only reasonable solution is weak_ptr! ● Important features: → cannot directly access the pointed object: *p, p->val, or p[idx] are not implemented! → convert to shared_ptr (after check for dangling!) when you need to access object → implemented very similar to shared_ptr, but no contribution to ref_counter update → same memory overhead as shared_ptr, and performance penalty for accessing due to conversion 9 A B C John Alice
  • 10.
    std::weak_ptr – usefulmethods ● Observes ● Modifiers 10 long use_count() const; → returns the number of shared pointers managing current object bool expired() const; → check if use_count()==0 std::shared_ptr<T> lock() const; → creates a new shared_ptr owning the managed object template< class Y > void reset(); → releases the reference to the managed object
  • 11.
    Do we stillneed raw pointers? ● Smart pointers implement automatic management of dynamically allocated memory. This comes with little overhead, or even no overhead as in the case of unique_ptr. ● However, there are still cases when raw pointers are needed. → when the pointer really just mean an address, not ownership of an object → to implement iterators → to handle an array decaying into a pointer → when working with legacy code (e.g. pre-C++11) → when passing arguments by address to modify their content 12