Smart pointers
Although all the actions with the dynamic memory can be implemented
with the C++ pointers and the commands new
and delete
we presented
to you earlier, using them is often pretty messy.
Especially when handling complicated dynamic structures, we often end up
in a situation where we allocate memory with new
but do not remember
to use delete
to deallocate it.
To make this duty easier, C++ includes so-called smart pointers
among its tools.
The smart pointers of C++ are library data types that automate the
deallocating of memory when nothing points to it anymore.
In plain language: the allocated memory will be automatically deallocated
when there are no (smart) pointer variables
pointing to that memory location in the program.
Smart pointer types are great because you use them mostly like you use
normal (raw) pointers, and on top of that, you do not have to worry about
deallocating memory.
To use smart pointers, you must include in the beginning of your
program the line
which lets us use the types
shared_ptr
unique_ptr
weak_ptr
On this course, we will only get to know the type shared_ptr
.
shared_ptr
pointers
A simple example on the use of shared_ptr
:
#include <iostream>
#include <memory> // You must remember this.
using namespace std;
int main() {
shared_ptr<int> int_ptr_1( new int(1) );
shared_ptr<int> int_ptr_2( make_shared<int>(9) );
cout << *int_ptr_1 << " " << *int_ptr_2 << endl;
cout << int_ptr_1 << " " << int_ptr_2 << endl;
cout << int_ptr_1.use_count() << " " << int_ptr_2.use_count() << endl << endl;
*int_ptr_2 = *int_ptr_2 - 4;
int_ptr_1 = int_ptr_2;
cout << *int_ptr_1 << " " << *int_ptr_2 << endl;
cout << int_ptr_1 << " " << int_ptr_2 << endl;
cout << int_ptr_1.use_count() << " " << int_ptr_2.use_count() << endl;
}
The execution of the program creates the following prints:
1 9
0x2589010 0x2589060
1 1
5 5
0x2589060 0x2589060
2 2
Please note that the example does not have any delete
commands,
even though dynamic memory is allocated both with new
and
the function make_shared
.
This is precisely the idea of the smart pointers, i.e. moving the
responsibility for deallocating
dynamically allocated memory to shared_ptr
objects.
We often use the term owner, which is just a fancy term to describe
whose responsibility memory deallocation is.
In the example above, we compared using shared_ptr
pointers
to using normal pointers.
Almost all the operations (the unary *
, ->
, comparison, and printing)
that work with normal pointers work with
shared_ptr
pointers as well. The greatest difference is that the operators
++
and --
do not work with shared_ptr
.
Also, assignment is possible if you do it from another shared_ptr
of
the same type.
Below we list a couple of useful characteristics of
shared_ptr
that you might have use for later:
If you want to get the memory address from a shared_ptr
pointer
as a normal C++ pointer, you can do it with the method get
:
shared_ptr<double> shared_double_ptr( new double );
...
double *normal_double_ptr = nullptr;
...
normal_double_ptr = shared_double_ptr.get();
You cannot use the operator =
to assign
a normal pointer to a shared_ptr
pointer.
However, you can assign a nullptr
to a shared_ptr
pointer.
A shared_ptr
pointer cannot be directly compared to a normal pointer.
The comparison is possible if the shared_ptr
is changed into a
normal pointer with the method get
, for example:
if(normal_pointer == shared_pointer.get()) {
...
}
A shared_ptr
pointer can be compared with a nullptr
.
The type shared_ptr
has a troublesome characteristic:
If you create a ”loop” of them, the memory will never be deallocated:
#include <iostream>
#include <memory>
using namespace std;
struct Test {
// Other fields
// ···
shared_ptr<Test> s_ptr;
};
int main() {
shared_ptr<Test> ptr1(new Test);
shared_ptr<Test> ptr2(new Test);
ptr1->s_ptr = ptr2;
ptr2->s_ptr = ptr1;
}
It is useful to draw a picture of the situation above so that you understand
the kind of ”egg or chicken” problem we have here.
The memory that ptr1
points to
cannot be deallocated because the pointer ptr2->s_ptr
points to it.
Then again, the memory that ptr2
points to cannot be deallocated either
because ptr1->s_ptr
points to it.
Task list with shared_ptr
pointers
See the project examples/10/task_list_v2
in Qt Creator.
If you are also interested in executing and editing the program code,
copy it under your student
directory.
The project includes an implementation of a list structure that
uses shared_ptr
pointers,
equivalent to an example on the previous round.
The only algorithmically new thing in the modified example is that there is
no need to implement a destructor for the List
class (or to use delete
commands on any other occasion) because the shared_ptr
pointers
deallocate memory when nothing points to it anymore.
Please note that all the different situations in handling a linked
list must be taken into account, just like when using normal pointers
(inserting the first element
into an empty list, removing the last element from the list, etc).
shared_ptr
, into which we can store the memory address of aint
-type variable. Initializing it to point to the variable that is reserved dynamically withnew
and that has the value of 1.