Smart Pointers in C++ - Part 1
Introduction
C++ defines smart pointers which can be used to replace raw pointers. Smart pointers can be used to ensure that the code is free of memory leaks. Smart pointers are defined in the <memory>
header under the std
namespace. Smart pointers are just classes that wrap raw pointers and provide functionality to make the process of resource acquisition and release free of errors.
Let's have a look at a problem that usually occurs with raw pointers.
Example 1
int main() {
// dynamically allocate heap memory to Person object
// name - "John Doe", age - 45
Person *p = new Person("John Doe", 45);
// do something with the Person object
return 0;
}
What's wrong with the above code? The fact that the memory allocated for p
was not freed before main
returned. So we need to add delete p;
statement before return 0;
. But the programmer sometimes forgets to do this, and we get a memory leak.
Another problem could be double free error. This error occurs when we try and free a specific dynamically allocated memory more than once.
Example 2
int main() {
// dynamically create an int
int *p1 = new int(10);
// some code
// p2 points to the same int where p1 points
int *p2 = p1;
// some more code
delete p2;
// trying to access value at p1
cout << *p1; // error
delete p1; // error
return 0;
}
The above code has two problems. First, we are trying to access the value pointed to by p1
, but p1
is a dangling pointer. Why? Because we just cleared the memory allocated for the int
using delete p2
! So we cannot access the value pointed to by p1
anymore. Second, when we try and delete the memory allocated to p1
. p1
is not pointing to a valid int
. So this gives us a double free error.
These problems can be easily avoided by using smart pointers.
Smart pointers to the rescue
Let's try and use a smart pointer for Example 1.
Example 1 (revised)
#include <memory>
int main() {
// dynamically allocate heap memory to Person object
// name - "John Doe", age - 45
std::unique_ptr<Person> p1 (new Person("John Doe", 45));
// do something with the Person object
return 0;
}
// p1 is deleted automatically
We don't use delete p1
to free the memory allocated for the Person object. When p1
goes out of scope, the unique_ptr
desctructor runs and frees up the memory. So, when using smart pointers, the programmer can forget about memory management and focus on the other important aspects of the application. Memory management will be automatically handled by smart pointers.
How do smart pointers avoid the double free error from Example 2? We will be replacing raw pointer with unique_ptr
. unique_ptr
objects (yes, they are technically objects) are special smart pointers with a constraint that no two unique_ptr
objects can point to the same object (more about this later).
Example 2 (revised)
#include <memory>
int main() {
// dynamically create an int
std::unique_ptr<int> p1 (new int(10));
// some code
std::unique_ptr<int> p2;
p2 = p1; // compiler error - copy assignment not allowed
// p2 points to the same int where p1 points
p2 = std::move(p1); // OK - move assignment must be used.
// p1 is invalidated. p2 takes ownership of the int.
// some more code
// delete p2; // we don't do this as p2 is not a raw pointer.
// trying to access value at p1
cout << *p1; // runtime error as p1 is invalidated
// But we can first check whether a smart pointer is valid or not
if (static_cast<bool>(p1)) {
// valid
} else {
// invalid
}
cout << *p2; // 10
return 0;
So we first created a new int, owned by p1
, using a constructor. Then, we just declared p2
. This creates a default unique_ptr
object p2
that owns nothing. Then we move assigned p2
by giving it the ownership of the int previously owned by p1
. This invalidates p1
.
Now, we have introduced some new concepts in this example.
- First, we say that a smart pointers takes ownership of an object (or a primitive data type; the memory allocated to it, in general), as opposed to saying points to.
- Second, we can dereference smart pointers just like raw pointers (that's because the
operator *
is overloaded forunique_ptr
and other types of smart pointers too. We are yet to discuss them.). - Third, move semantics. We have to use move semantics (meaning move constructor and move assignment) when dealing with
unique_ptr
smart pointers. Copying is not allowed as that would defeat the purpose of aunique_ptr
. As the name suggets, the pointer has to be unique, meaning it can be the only one owning a specific object.
Stay tuned for knowing more unique_ptr
and other types of smart pointers.
Can't wait to find out more? Click for Part 2.