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.
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
char* str = new char ; 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|
||You don’t need to hold multiple references to a single object|
||You need to hold multiple references to a single object|
||You need to hold multiple references to a single object but don’t want to deallocate the object|
||Deprecated – use
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);
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_ptrto a new owner.
For shared ownership: std::shared_ptr
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);
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
std::cout << p.use_count() << std::endl;
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);
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
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";