The smart pointers of C++ are library data types that automate the
deallocation 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.
It would be possible to use normal pointers in dynamic memory management,
but in such case it was programmer’s duty to deallocate memory, which
is can easily be forgotten.
On this course, we will only use smart pointers in dynamic memory management.
To use smart pointers, you must include in the beginning of your
program the line
#include<memory>
which lets us use the types
shared_ptrunique_ptrweak_ptr
On this course, we will only get to know the type shared_ptr.
Automatic memory deallocation is based on reference counters.
It is kept track on how many pointers there are pointing to a
dynamic variable.
When the reference counter has the value zero (i.e. there are no pointers
pointing to a dynamic variable), memory allocated for the variable can
be deallocated.
We can find out the value of the reference counter by calling the
function use_count.
There is a specific function make_shared for constructing a smart
(shared) pointer, with the following properties:
The lifetime of a variable begins at the moment when make_shared
is called.
A dynamic variable does not have a name, but the operation make_shared
returns a pointer, the value of which reveals where the
new dynamic variable is located within the main memory.
To make the dynamic variable useful, its address (the return value
of make_shared) needs to be stored in a pointer variable.
If the variable is a class-type one, make_shared will take care of
calling the constructor.
Now, the parameters of make_shared are those required by the constructor.
A simple example on the use of shared_ptr:
#include<iostream>#include<memory> // You must remember this.usingnamespacestd;intmain(){shared_ptr<int>int_ptr_1=make_shared<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;}
Defining a pointer variable of the type shared_ptr, into which
we can store the memory address of a int-type variable.
Initializing it to point to the variable that is reserved dynamically
with new and that has the value of 1.
Defining another shared_ptr, but now with the value of 9.
Using shared_ptr pointers.
Calling the method use_count to print the value of the
reference counter of pointers.
The method use_count returns the number of
shared_ptr pointers pointing to
the same memory location as the target object of the use_count method.
When the value of the reference counter reaches zero, the allocated
memory will be deallocated automatically.
The most relevant part about the example: we set int_ptr_2 to point
to the same memory address as int_ptr_1.
Now, there are two shared_ptr-type pointers pointing to the
memory cell where int_ptr_1 originally pointed to.
The code block, where int_ptr_2 was declared, ends.
Therefore the lifetime of the variable int_ptr_2 ends.
Now, there is only one shared_ptr-type pointer pointing to the
memory cell where int_ptr_1 originally pointed to.
The lifetime of the variables int_ptr_1 and int_ptr_2 ends,
which means that the memory area they pointed to has
no shared_ptr pointers:
the allocated memory is automatically deallocated.
The execution of the program creates the following prints:
Here and in the other execution examples that print pointer values to the
screen, please remember that the value of the memory address can be
different at different executions.
Let us consider reference counters with the following code:
Personperson;// Person objectshared_ptr<Person>p1=make_shared<Person>(person);shared_ptr<Person>p2=make_shared<Person>(person);shared_ptr<Person>p3=p1;cout<<p1.use_count()<<endl;// prints 2cout<<p2.use_count()<<endl;// prints 1cout<<p3.use_count()<<endl;// prints 2
Based on the code you could easily imagine that the pointers p1 and p2
point to the same object.
However, from the point of view of reference counters, this is not so.
The figure below clarifies the situation:
The function make_shared allocates memory as much as is needed for
an object or a value of a certain type, which is given as a parameter.
In the above code, memory is allocated twice as much as is needed
for a Person object.
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:
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>usingnamespacestd;structTest{// other fields// ···shared_ptr<Test>s_ptr;};voidfun(){shared_ptr<Test>ptr1=make_shared<Test>(...);shared_ptr<Test>ptr2=make_shared<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.
From this week’s examples you can find the one: examples/06/reservations.
It handles reserving books from a library by using reference counters.
When a book is reserved, a new pointer is put to point to the book.
In this way, the value of the reference counter tells the number of
reservations.
Defining a pointer variable of the type
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.