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_1 = *int_ptr_1 + 4;
int_ptr_2 = int_ptr_1;
cout << *int_ptr_1 << " " << *int_ptr_2 << endl;
cout << int_ptr_1 << " " << int_ptr_2 << endl;
cout << int_ptr_1.use_count() << endl;
}
cout << int_ptr_1.use_count() << endl;
}
The execution of the program creates the following prints:
1 9
0x2589010 0x2589060
1 1
5 5
0x2589060 0x2589060
2
1
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/09/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 withnewand that has the value of 1.