If you work with pointers in C++ before C++11, then you have to create and destroy it manually. This manual management of pointers has certain problems that can quickly escalate to some catastrophic memory bugs which are hard to debug. The common issues related to the manual management of pointers are:
- Memory Leaks
- Dangling Pointers
- Unclear Ownership
Hence we have to take utmost care when dealing with manual pointer management.
Traditional Pointers
We use the new& delete operator for dynamic allocation and deallocation of object pointers on heap. Let’s explore an example:
1 |
|
We created a Car class with constructor and destructor because this will help us to see the messages on creation and destruction of a car object. Now if we are not using C++11 then we have to:
- Create object using
new - Delete the object using
delete
1 | int main() { |
If we execute the code above we can see the logs are hinting towards, creation and destruction of the object.
1 | Car created |
Issues with Manual Management
We already saw the prominent problems with manual management, let’s have a look at the issues we mentioned above.
Memory Leaks
This is the scenario when the program is borrowing memory from the OS but is not returning that. The programs are allocated a fixed amount of memory and if we keep borrowing and do not return them at some time in the future our program will crash due because the OS will through error of process out of memory.
During manual management it is common to forget the call to delete which leads to memory leaks.
Dangling Pointer
This is the case where the pointer variable we created cannot be traced. For example we are calling the hello method and if it throws and exception we cannot reach back to the pointer variable c1.
Here’s an example:
1 | // create a pointer |
We can solve such issues by explicitly assigning NULL to the variable p.
1 | // create a pointer |
Unclear Ownership
Suppose we have a function that process the car for maybe painting or delivery and accidentally someone calls delete on that. There’s no restriction to this and this can be easily done which created undefined behavior and logical issues at runtime.
1 | void process_car(Car *c) { |
C++11 at Rescue
The C++ committee has been listening to a lot of problems and C++11 is the modern and major revision to the C++ programming language that has redefined how we think and code in C++. The v11 has major and significant changes that has profound impact. It has tried it’s best to make the language safer and less verbose.
One of the safety addition to the C++ 11 is the concept of Smart Pointers.
Smart Pointers automates the allocation and deallocation of memory so that the programmer is not burdened with the manual management which is prone to fatal errors as we saw above.
Smart Pointers
Smart pointers in C++ are class templates that automatically manage dynamic memory. They are like the regular pointers but automatically delete the pointer when the variable goes out of scope. We can use the <memory> header file to use them. There are three types of smart pointers introduced:
- Unique Pointer
std::unique_ptr - Shared Pointer
std::shared_ptr - Weak Pointer
std::weak_ptr
Unique Pointer
Let’s try and code the same program above using unique pointer instead of regular pointer.
1 |
|
A unique_ptr owns a piece of memory exclusively. This is like a single room key that can be owned by a single person at any given tie. This pointer automatically cleans up the memory when it goes out of scope which helps prevent memory leaks. Unique pointers cannot be copied but we can still transfer ownership using std::move when required.
Shared Pointer
A shared_ptr allows multiple parts of a program to share the same piece of memory. The smart pointer class internally keeps a reference count, and once the last shared_ptr is destroyed, the memory is automatically released.
1 | int main() { |
This makes it useful when several objects or functions need to work with the same resource.
Weak Pointer
weak_ptr observes a shared_ptr without actually owning the memory. Since it doesn’t increase the reference count, it helps avoid circular ownership problems. To safely use the object, we first turn the weak_ptr into a shared_ptr by calling lock().
1 | int main() { |
Closing Notes
If you are learning C++ or stating a new project in C++ I would highly encourage you to go through the C++ 11 specifications so that you can develop modern and safe applications.
Happy Coding. Bye!