Smart pointers: auto_ptr, unique_ptr, shared_ptr, weak_ptr and raw pointers
Since c++11 we have a lot of smart pointers, we see here the various types of
pointers that exists out there, sorted by decreasing order of usefulness
(according to me):
- std::unique_ptr
(since c++11)
- raw pointer
- std::shared_ptr
(since c++11)
- std::weak_ptr
(since c++11)
- std::auto_ptr
(since c++98, deprecated in c++11, removed in c++17)
Contents
Why smart pointers
Take a look at this code:
class Foo {
private:
int bar;
};
void fun() {
// dynamically creating object of class Foo
Foo* p = new Foo();
}
int main() {
for (int i = 0; i < 10; ++i) {
fun();
}
return 0;
}
Function fun()
creates a pointer to the Foo
object, when fun()
ends
p
will be destroyed as it is a local variable, but, the memory it consumed
won't be deallocated because we forgot to use delete p
at the end of
the function. This is clearly a memory leak, and that's no good.
But this is just a simple case, we could just add a delete
, but this can
become very messy if, for example, we are using exceptions in our code. Look
at this new fun()
and main()
functions:
void fun() {
Foo* p = new Foo();
if (/* some condition */ true) {
throw std::runtime_error("fatal error");
}
delete p;
}
int main() {
try {
for (int i = 0; i < 10; ++i) {
fun();
}
} catch (...) {
return 1;
}
return 0;
}
Now we have a delete
, problem solved, but what if an exception is thrown
inside fun()
, we will not reach the delete
on the next line, and again,
we will have a memory leak, and that's no good.
But what if the pointer deletes itself when out of scope? Local variables are deleted when exiting the scope they are in, and pointers are deleted too, but keep in mind a pointer stores a number, that references a memory position, so that number is what is deleted when existing the scope. Smart pointers provides a solution for this.
What is a smart pointer
Run the following code:
#include <iostream>
class Foo {
public:
Foo() {
std::cout << "constructor\n";
}
~Foo() {
std::cout << "destructor\n";
}
};
void bar() {
std::cout << "Entering function...\n";
Foo var();
std::cout << "Exiting function...\n";
}
int main() {
bar();
return 0;
}
Output:
Entering function...
constructor
Exiting function...
destructor
As you can see, Foo()
destructor is called when the whe exit the scope, but
Foo var
is not a pointer, this is just to see that variables get
destroyed when out of scope (but as I said before, a pointer will not delete
the element is pointed to, it will be just the pointer value, a memory
address, what gets destroyed.
A smart pointer
is a wrapper class over a pointer with a destructor and
overloaded operators like *
and ->
. Since the destructor is automatically
called when an object goes out of scope, the dynamically allocated memory
would automatically be deleted (or reference count decremented), by a delete
in the smart pointer destructor.
A small implementation of a smart pointer could look like this:
#include <iostream>
class SmartPtr {
int* ptr; // actual pointer
public:
// Constructor
explicit SmartPtr(int* p = NULL) {
ptr = p;
}
// Destructor
~SmartPtr() { delete (ptr); }
// Overloading dereferencing operator
int& operator*() {
return *ptr;
}
};
int main() {
SmartPtr p(new int());
*p = 20;
cout << *ptr << "\n";
// We don't need to call `delete p`, when the object p goes out of scope
// the destructor for it is automatically called and the destructor
// does `delete ptr`, so, no leaks, that's good.
return 0;
}
Types of smart pointers
std::unique_ptr
since c++11
The semantics of std::unique_ptr
is that it is the sole owner of a memory
resource. A std::unique_ptr
will hold a pointer and delete it in its
destructor (unless you customize this passing another function to the
template parameters of std::unique_ptr
).
This also allows you to express your intentions on keeping that pointer in the scope in an interface. Look at the following code:
std::unique_ptr<Foo> bar();
It tells you that it gives a pointer to a Foo
object, of which you are the
owner. No one else will delete/free this pointer except the unique_ptr
destructor when unique_ptr
goes out of scope.
Since you get the ownership, this gives you confidence that you are free to modify the value of the pointed to object, and you can exit the scope at any point without worrying of delete/free the pointer (because the destructor will do it).
This works the other way around too, by passing an std::unique_ptr
as a
parameter (this also limites the std::unique_ptr
to the function scope):
class Foo {
public:
Foo(std::unique_ptr<Bar> baz);
// ...
};
In this case, Foo
takes ownership of the Bar
pointer.
Note though that even when you receive a std::unique_ptr
, you are not
guaranteed that no one else has access to the pointer. Indeed, if another
context keeps a copy of the pointer inside you unique_ptr
, then modifying the
pointed to object through the unique_ptr
object will of course impact this
other context, and when one of them goes out of scope, it will delete/free the
object! that's why unique_ptr
doesn't have a copy constructor or an assign
operator, it's not mean to have multiple instances, that is only bound to
couse trouble.
But if for some reason you have multiple instances of the unique_ptr
, and you
want avoid modification of the pointed element, you can do it by using a
unique_ptr
to const
:
// for some reason I don't want you to modify the `Foo` you are being passed
std::unique_ptr<const Foo> bar();
As I said before, to ensure that there is only one unique_ptr
that owns
a memory resource, std::unique_ptr
cannot be copied. But, the ownership can
however be transfered from ane unique_ptr
to another (which is
how you can pass them or return them from a function) by moving
a unique_ptr
into another one! And now the above example of the const
pointed element makes sense!
A move can be achieved by returning an std::unique_ptr
by value from
a function, by passing one as argument, or explicitly in code using the move
operator!
// create a unique_ptr
std::unique_ptr p1 = std::make_unique(42);
// move resource to p2
std::unique_ptr p2 = std::move(p1);
// now p2 holds the resource and p1 no longer holds anything
raw pointers
since always
Even if raw pointers are not smart pointers, they aren't "dumb" either. In fact there are legitimate reasons to use them although these reasons don't happen often. They share a lot with references, but the latter should be preferred whenever is possible, except in some cases.
Raw pointers and references represent access to an object, but not ownership.
In fact, this is the default way you should use to pass objects to functions and methods:
void foo(const Bar& bar);
This is porticularly relevant to note when you hold an object with
a unique_ptr
and want to pass it to an interface. You don't pass the
unique_ptr
, nor a reference to it, but rather a reference to the pointed
object:
std::unique_ptr<Foo> foo = createFoo();
printFoo(*foo); // a reference to the pointed object
std::shared_ptr
since c++11
A single memory resource can be held by several std::shared_ptr
at the same time.
The shared_ptr
s internally maintein a count of how mony of them there are
holding the same resource, and when the last one is destroyed (when it goes
out of scope), it deletes/frees the memory resource.
Therefore std::shared_ptr
allows copies, but with a reference-counting
mechanism to make sure that every resource is deleted once and only once.
That makes std::shared_ptr
an alternative for std::unique_ptr
when we
are gonna have more than one pointer to the same object, and we don't
want to delete/free it when the one of this unique_ptr
goes out of scope,
so never use raw pointers again for this!
At first glance std::shared_ptr
looks like the panacea for memory management,
as it can be passed around and still maintein memory safety. But
std::shared_ptr
should not be used by defualt for several reasons:
- Having several simultaneous holders for a resource makes for a more complex system than with one unique holder, like with std::unique_ptr
. Even though an std::unique_ptr
doesn’t prevent from accessing and modifying its resource, it sends a message that it is the priviledged owner of a resource. For this reason you’d expect it to centralize the control of the resource, at least to some degree.
- Having several simultaneous holders of a resource makes thread-safety harder.
- It makes the code counter-intuitive when an object in not shared in terms of the domain and still appears as "shared" in the code for a technical reason.
- It can incur a performance cost, both in time and memory, because of the bookkeeping related to the reference-counting.
One good case for using std::shared_ptr
though is when objects are
shared in the domain. Using shared pointers then reflects it in a
expressive way. Typically, the nodes of a graphs are well represented as
shared pointers, because several nodes can hold a reference to ane other node.
std::weak_ptr
since c++11
std::weak_ptr
can hold a reference to a shared object along with
std::shared_ptr
, but they don't increment the reference count.
This means that if no more std::shared_ptr
are holding an object, this
object will be deleted even if some weak pointers still point to it.
For this reasons, a weak pointer needs to chech if the object it points to is
still alive. To do this, it has to be copied into a std::shared_ptr
:
void foo(std::weak_ptr<int> wp) {
if (std::shared_ptr<int> sp = wp.lock()) {
// the resource is still alive
} else {
// the resource is no longer alive
}
}
A typical use for this in about breaking shared_ptr
circular references.
Consider the following code:
struct Foo {
std::shared_ptr<Foo> foo;
};
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<Foo> p2 = std::make_shared<Foo>();;
p1->foo = p2;
p2->foo = p1;
None of the houses ends up being destroyed at the end of this code, because
the shared_ptr
points into one another. But if one is a weak_ptr
instead,
there is no longer a circular reference.
std::auto_ptr
since c++98, deprecated in c++11, removed in c++17
It aimed at filling the same need as unique_ptr
, but back when move semantics
didn't exist in C++.
It essentially does in its copy constructor what unique_ptr
does in its
move constructor, for that reasons is inferior to unique_ptr
and you
shouldn't use it if you have access to unique_ptr
, because it can lead to
erroneous code:
std::auto_ptr<int> p1(new int(42));
std::auto_ptr<int> p2 = p1;
// it looks like p1 == p2, but no, p1 is empty and p2 uses the resource
So please avoid using it.