A C++ smart pointer is a wrapper class. While the objects of this class look like regular pointers, they have additional functionalities – e.g., reference counting, automatic object destruction, and C++ memory management.
Contents
Memory leaks: how do these happen?
In C++, you can reach a variable by using either their name or address which represents the unique location of the variable’s value in the system memory. A C++ pointer is a variable that contains the address of another variable as its value. It can also be called a raw pointer.
While these pointers are very convenient to use, they might be hard to manage. Each object that you dynamically allocate using the operator new
must then be manually deallocated with the operator delete
:
char* str = new char [5];
delete [] str;
If you fail to deallocate memory, C++ throws a risk of a memory leak. A single leak might just cost you a few bytes. Unfortunately, these losses add up – e.g., repeating a function that causes a memory leak of five bytes a million times will waste five million bytes. In time, this can crash your application.
Tip: it can be easy to miss a single leak, but it’s always better to catch them in time. One of the best options on how to check for memory leaks is using C++ debuggers.
How to use different C++ smart pointer types
To easily deal with the risk of memory leaks, you can use smart pointers. First and foremost, it simplifies the C++ dynamic memory allocation by making deallocation automatic. This means you do not need to manually use the delete
operator each time.
There are four types of smart pointers in C++:
Smart pointer | Best used when |
---|---|
std::unique_ptr |
You don’t need to hold multiple references to a single object |
std::shared_ptr |
You need to hold multiple references to a single object |
std::weak_ptr |
You need to hold multiple references to a single object but don’t want to deallocate the object |
std::auto_ptr |
Deprecated – use std::unique_ptr instead |
For a unique reference: std::unique_ptr
Keeping C++ smart pointers unique is a recommended practice, as it keeps the program logic clean and simple. When using the std::unique_ptr
smart pointer, you can only assign one owner for the pointer behind the wrapper. The object the std::unique_ptr
points to is deleted automatically when the smart pointer leaves the scope.
// Syntax to follow:
std::unique_ptr<data_type> p(new data_type);
// A basic example:
std::unique_ptr<int> p(new int);
The std::unique_ptr
smart pointer does not have a copy constructor, so you cannot share or duplicate it – the system will throw an error, just like in the example below:
std::unique_ptr<int> p1(new int(365));
std::unique_ptr<int> p2 = p1;
Note: instead of copying, you can move
std::unique_ptr
to a new owner.
For shared ownership: std::shared_ptr
Using the std::shared_ptr
smart pointer means you can apply multiple owners to a single raw pointer. All of the owners must also leave the scope for it to be deleted.
// Syntax to follow:
std::shared_ptr<data_type> p(new data_type);
// A basic example:
std::shared_ptr<int> p1(new int);
The std::shared_ptr
smart pointer can also be used for reference counting. It contains an internal counter which tracks the amount of owners not yet destroyed. To check how many pointers lead to the same address, use the use_count()
method:
In case of a cycle: std::weak_ptr
There is a type of C++ memory leak called the circular reference, also known as a cycle. It happens when C++ smart pointers form a referential loop by the last object in the reference chain points to the first one (e.g., X points to Y, Y points to Z, and Z points to X):
To solve this problem, you need to create a std::weak_ptr
smart pointer within the std::shared_ptr
smart pointer, just as you see in the example below:
std::shared_ptr<char> p_shared = std::make_shared<char>(15);
std::weak_ptr<char> p_weak1(p_shared);
std::weak_ptr<char> p_weak2(p_weak1);
Both std::weak_ptr
and std::shared_ptr
smart pointers will point to the same data. However, the std::weak_ptr
one will not change the value of the internal counter and hence take part in reference counting. It is also not considered as an owner.
One more reason to use std::weak_ptr
for C++ memory management is that it helps with dangling pointers (those that point to deleted data). You can check if a particular piece of data is valid by using lock()
or expired()
:
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "Sorry, weak1 is no longer valid!\n";
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "Sorry, weak2 is is no longer valid!\n";