Home | Projects | Notes > C++ Programming > Smart Pointers
Issues with raw pointers
What are smart pointers?
Concept of ownership and RAII
C++ smart pointers
Unique pointers (unique_ptr
)
Shared pointers (shared_ptr
)
Weak pointers (weak_ptr
)
Custom deleters
C++ provides absolute flexibility with memory management.
Allocation
Deallocation
Lifetime management
Some potentially serious problems
Uninitialized (wile) pointers
Memory leaks
Dangling pointers
Not exception safe
Ownership
Who owns the pointer?
When should a pointer be deleted?
Objects
Can only point to heap-allocated memory
Automatically call delete when no longer needed
Adhere to RAII principles
C++ smart pointers
Unique pointers (unique_ptr
)
Shared pointers (shared_ptr
)
Weak pointers (weak_ptr
)
Auto pointers (auto_ptr
) - Deprecated. Will not be discussing this one!
#include <memory>
Defined by class templates
Wrapper around a raw pointer
Overloaded operators
Dereference (*
)
Member selection (->
)
Pointer arithmetic not supported (++
, --
, etc.)
Can have custom deleters
A simple example of smart pointers
xxxxxxxxxx
81{
2 std::smart_pointer<Some_Class> ptr = . . .
3
4 ptr->method();
5 cout << (*ptr) << endl;
6}
7
8// ptr will be destroyed automatically when no longer needed
Common idiom or pattern used in software design based on container object lifetime
RAII objects are allocated on the stack
Resource Acquisition
Open a file
Allocate memory
Acquire a lock
Is Initialization
The resource is acquired in a constructor
Resource relinquishing
Happens in the destructor
Close the file
Deallocate the memory
Release the lock
unique_ptr
)Simple smart pointer - very efficient!
unique_ptr<T>
Points to an object of type T
on the heap
It is unique - there can only be one unique_ptr<T>
pointing to the object on the heap
Owns what it points to
Cannot be assigned or copied
CAN be moved
When the pointer is destroyed, what it points to is automatically destroyed
Creating, initializing and using
xxxxxxxxxx
61{
2 std::unique_ptr<int> p1{new int{100}};
3 std::cout << *p1 << std::endl; // 100
4 *p1 = 200;
5 std::cout << *p1 << std::endl; // 200
6} // p1 gets deleted automatically
Some other useful methods
xxxxxxxxxx
81{
2 std::unique_ptr<int> p1{new int{100}};
3 std::cout << p1.get() << std::endl; // 0x564388
4 p1.reset(); // p1 is now nullptr
5
6 if (p1)
7 std::cout << *p1 << std::endl; // Won't execute
8} // p1 gets deleted automatically
User defined classes
xxxxxxxxxx
71{
2 std::unique_ptr<Account> p1{new Account{"Jack"}};
3 std::cout << *p1 << std::endl; // Display account
4
5 p1->deposit(1000);
6 p1->withdraw(500);
7} // p1 gets deleted automatically
Vectors and move
xxxxxxxxxx
61{
2 std::vector<std::unique_ptr<int>> vec;
3 std::unique_ptr<int> ptr{new int{100}};
4 vec.push_back(ptr); // Error - copy not allowed
5 vec.push_back(std::move(ptr));
6} // p1 gets deleted automatically
make_unique
(C++14)
xxxxxxxxxx
51{
2 std::unique_ptr<int> p1 = make_unique<int>(100);
3 std::unique_ptr<Account> p2 = make_unique<Account>("Jack", 4000);
4 auto p3 = make_unique<Player>("Hero", 100, 100);
5} // p1 gets deleted automatically
shared_ptr
)Provides shared ownership of heap objects
shared_ptr<T>
Points to an object of type T
on the heap
It is not unique - there can be many shared_ptr
s pointing to the same object on the heap.
Establishes shared ownership relationship
CAN be assigned and copied
CAN be moved
Doesn't support managing arrays by default
When the use count is zero, the managed object on the heap is destroyed
Creating, initializing and using
xxxxxxxxxx
61{
2std::shared_ptr<int> p1{new int{100}};
3std::cout << *p1 << std::endl; // 100
4*p1 = 200;
5std::cout << *p1 << std::endl; // 200
6} // p1 gets deleted automatically
Some other useful methods
xxxxxxxxxx
111// use_count - the number of shared_ptr objects managing the heap object
2std::shared_ptr<int> p1 {new int{100}};
3std::cout << p1.use_count() << std::endl; // 1
4
5std::shared_ptr<int> p2{p1}; // Shared ownership
6std::cout << p1.use_count() << std::endl; // 2
7
8p1.reset(); // Decrement the use_count; p1 is nulled out
9std::cout << p1.use_count() << std::endl; // 0
10std::cout << p2.use_count() << std::endl; // 1
11} // p1, p2 get deleted automatically
User defined classes
xxxxxxxxxx
71{
2 std::shared_ptr<Account> p1{new Account{"Jack"}};
3 std::cout << *p1 << std::endl; // Display account
4
5 p1->deposit(1000);
6 p1->withdraw(500);
7} // p1 gets deleted automatically
Vectors and move
xxxxxxxxxx
61{
2 std::vector<std::shared_ptr<int>> vec;
3 std::shared_ptr<int> ptr{new int{100}};
4 vec.push_back(ptr); // OK - copy IS allowed
5 std::cout << ptr.use_count() << std::endl; // 2
6} // ptr gets deleted automatically
make_shared
(C++11)
xxxxxxxxxx
61{
2 std::shared_ptr<int> p1 = std::make_shared<int>(100); // use_count: 1
3 std::shared_ptr<int> p2{p1}; // use_count: 2
4 std::shared_ptr<int> p3;
5 p3 = p1; // use_count: 3
6} // p1, p2, p3 get deleted automatically
Use
std::make_shared
- It's more efficient!All 3 pointers point to the SAME object on the heap!
When the use_count becomes 0 the heap object is deallocated
weak_ptr
)Provides a non-owning "weak" reference
weak_ptr<T>
Points to an object of type T
on the heap
Does not participate in owning relationship
Always created from a shared_ptr
Does NOT increment or decrement reference use count
Used to prevent strong reference cycles which could prevent objects from being deleted
Circular or cyclic reference
A refers to B
B refers to A
Shared strong ownership prevents heap deallocation
Solution - make one of the pointers non-owning or 'weak'
Now heap storage is deallocated properly
Sometimes when we destroy a smart pointer we need more than to just destroy the object on the heap.
These are special use-cases
C++ smart pointers allow you to provide custom deleters
Lots of way to achieve this
Functions
Lambdas
Others ...
Functions
xxxxxxxxxx
71void my_deleter(Some_Class *raw_pointer)
2{
3 // Your custom deleter code
4 delete raw_pointer;
5}
6
7share_ptr<Some_Class> ptr{new Some_class{}, my_deleter};
xxxxxxxxxx
71void my_deleter(Test *ptr)
2{
3 cout << "In my custom deleter" << endl;
4 delete ptr;
5}
6
7share_ptr<Test> ptr{new Test{}, my_deleter};
Lambdas
xxxxxxxxxx
41shared_ptr<Test> ptr(new Test{100}, [](Test *ptr) {
2 cout << "\tUsing my custom deleter" << endl;
3 delete ptr;
4});