Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 778f1bc0 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Cleaning-up part 5 (still in progress).

parent 0c371225
Branches
Tags
No related merge requests found
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [RAII idiom](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) # [Getting started in C++](/) - [Useful concepts and STL](./0-main.ipynb) - [RAII idiom](./2-RAII.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#Example:-dynamic-array" data-toc-modified-id="Example:-dynamic-array-2">Example: dynamic array</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#Example:-dynamic-array" data-toc-modified-id="Example:-dynamic-array-2">Example: dynamic array</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
This chapter is one of the most important of this tutorial: it is an idiom without which the most common critic against C++ is totally justified! This chapter is one of the most important of this tutorial: it is an idiom without which the most common critic against C++ is totally justified!
Often, people who criticizes the language say C++ is really tricky and that is extremely easy to leak memory all over the way, and that it sorely miss a [**garbage collector**](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) which does the job of cleaning-up and freeing the memory when the data are no longer used. Often, people who criticizes the language say C++ is really tricky and that is extremely easy to leak memory all over the way, and that it sorely miss a [**garbage collector**](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) which does the job of cleaning-up and freeing the memory when the data are no longer used.
However, garbage collection, used for instance in Python and Java, is not without issues itself: the memory is not always freed as swiftly as possible, and the bookkeeping of references is not without a toll on the efficiency of the program itself. However, garbage collection, used for instance in Python and Java, is not without issues itself: the memory is not always freed as swiftly as possible, and the bookkeeping of references is not without a toll on the efficiency of the program.
C++ provides in fact the best of both worlds: a way to provide safe freeing of memory as soon as possible... provided you know how to adequately use it. C++ provides in fact the best of both worlds: a way to provide safe freeing of memory as soon as possible... provided you know how to adequately use it.
The **Ressource Acquisition Is Initialization** or **RAII** is the key mechanism for this: the idea is just to use an object with: The **Ressource Acquisition Is Initialization** or **RAII** is the key mechanism for this: the idea is just to use an object with:
* The constructor in charge of allocating the ressources (memory, mutexes, etc...) * The constructor in charge of allocating the ressources (memory, mutexes, etc...)
* The destructor in charge of freeing all that as soon as the object becomes out-of-scope. * The destructor in charge of freeing all that as soon as the object becomes out-of-scope.
And that's it! And that's it!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Example: dynamic array ## Example: dynamic array
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <iostream> #include <iostream>
class Array class Array
{ {
public: public:
Array(std::string name, std::size_t dimension); Array(std::string name, std::size_t dimension);
~Array(); ~Array();
private: private:
std::string name_; std::string name_;
double* array_ = nullptr; double* array_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Array::Array(std::string name, std::size_t dimension) Array::Array(std::string name, std::size_t dimension)
: name_(name) : name_(name)
{ {
std::cout << "Acquire ressources for " << name_ << std::endl; std::cout << "Acquire ressources for " << name_ << std::endl;
array_ = new double[dimension]; array_ = new double[dimension];
for (auto i = 0ul; i < dimension; ++i) for (auto i = 0ul; i < dimension; ++i)
array_[i] = 0.; array_[i] = 0.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Array::~Array() Array::~Array()
{ {
std::cout << "Release ressources for " << name_ << std::endl; std::cout << "Release ressources for " << name_ << std::endl;
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Array array1("Array 1", 5); Array array1("Array 1", 5);
{ {
Array array2("Array 2", 2); Array array2("Array 2", 2);
{ {
Array array3("Array 3", 2); Array array3("Array 3", 2);
} }
Array array4("Array 4", 4); Array array4("Array 4", 4);
} }
Array array5("Array 5", 19); Array array5("Array 5", 19);
} }
``` ```
%% Output %% Output
Acquire ressources for Array 1 Acquire ressources for Array 1
Acquire ressources for Array 2 Acquire ressources for Array 2
Acquire ressources for Array 3 Acquire ressources for Array 3
Release ressources for Array 3 Release ressources for Array 3
Acquire ressources for Array 4 Acquire ressources for Array 4
Release ressources for Array 4 Release ressources for Array 4
Release ressources for Array 2 Release ressources for Array 2
Acquire ressources for Array 5 Acquire ressources for Array 5
Release ressources for Array 5 Release ressources for Array 5
Release ressources for Array 1 Release ressources for Array 1
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, don't use such a class: STL `std::vector` and `std::array` are already there for that (and use up RAII principle under the hood!) and provide also more complicated mechanisms such as the copy. Of course, don't use such a class: STL `std::vector` and `std::array` are already there for that (and use up RAII principle under the hood!) and provide also more complicated mechanisms such as the copy.
The ressource itself needs not be memory; for instance `std::ofstream` also use up RAII: its destructor calls `close()` if not done manually before, ensuring the file on disk features properly the changes you might have done on it during the run of your program. The ressource itself needs not be memory; for instance `std::ofstream` also use up RAII: its destructor calls `close()` if not done manually before, ensuring the file on disk features properly the changes you might have done on it during the run of your program.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2020_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [Containers](/notebooks/5-UsefulConceptsAndSTL/3-Containers.ipynb) # [Getting started in C++](/) - [Useful concepts and STL](./0-main.ipynb) - [Containers](./3-Containers.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#std::vector" data-toc-modified-id="std::vector-2"><code>std::vector</code></a></span><ul class="toc-item"><li><span><a href="#Allocator-template-parameter" data-toc-modified-id="Allocator-template-parameter-2.1">Allocator template parameter</a></span></li><li><span><a href="#Most-used-constructors" data-toc-modified-id="Most-used-constructors-2.2">Most used constructors</a></span></li><li><span><a href="#Size" data-toc-modified-id="Size-2.3">Size</a></span></li><li><span><a href="#Adding-new-elements" data-toc-modified-id="Adding-new-elements-2.4">Adding new elements</a></span></li><li><span><a href="#Direct-access:-operator[]-and-at()" data-toc-modified-id="Direct-access:-operator[]-and-at()-2.5">Direct access: <code>operator[]</code> and <code>at()</code></a></span></li><li><span><a href="#Under-the-hood:-storage-and-capacity" data-toc-modified-id="Under-the-hood:-storage-and-capacity-2.6">Under the hood: storage and capacity</a></span></li><li><span><a href="#reserve()-and-resize()" data-toc-modified-id="reserve()-and-resize()-2.7"><code>reserve()</code> and <code>resize()</code></a></span></li><li><span><a href="#std::vector-as-a-C-array" data-toc-modified-id="std::vector-as-a-C-array-2.8"><code>std::vector</code> as a C array</a></span></li><li><span><a href="#Iterators" data-toc-modified-id="Iterators-2.9">Iterators</a></span></li></ul></li><li><span><a href="#Incrementing-/-decrementing-iterators" data-toc-modified-id="Incrementing-/-decrementing-iterators-3">Incrementing / decrementing iterators</a></span></li><li><span><a href="#Other-containers" data-toc-modified-id="Other-containers-4">Other containers</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#std::vector" data-toc-modified-id="std::vector-2"><code>std::vector</code></a></span><ul class="toc-item"><li><span><a href="#Allocator-template-parameter" data-toc-modified-id="Allocator-template-parameter-2.1">Allocator template parameter</a></span></li><li><span><a href="#Most-used-constructors" data-toc-modified-id="Most-used-constructors-2.2">Most used constructors</a></span></li><li><span><a href="#Size" data-toc-modified-id="Size-2.3">Size</a></span></li><li><span><a href="#Adding-new-elements" data-toc-modified-id="Adding-new-elements-2.4">Adding new elements</a></span></li><li><span><a href="#Direct-access:-operator[]-and-at()" data-toc-modified-id="Direct-access:-operator[]-and-at()-2.5">Direct access: <code>operator[]</code> and <code>at()</code></a></span></li><li><span><a href="#Under-the-hood:-storage-and-capacity" data-toc-modified-id="Under-the-hood:-storage-and-capacity-2.6">Under the hood: storage and capacity</a></span></li><li><span><a href="#reserve()-and-resize()" data-toc-modified-id="reserve()-and-resize()-2.7"><code>reserve()</code> and <code>resize()</code></a></span></li><li><span><a href="#std::vector-as-a-C-array" data-toc-modified-id="std::vector-as-a-C-array-2.8"><code>std::vector</code> as a C array</a></span></li><li><span><a href="#Iterators" data-toc-modified-id="Iterators-2.9">Iterators</a></span></li></ul></li><li><span><a href="#Incrementing-/-decrementing-iterators" data-toc-modified-id="Incrementing-/-decrementing-iterators-3">Incrementing / decrementing iterators</a></span></li><li><span><a href="#Other-containers" data-toc-modified-id="Other-containers-4">Other containers</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
Containers are the standard answer to a very common problem: how to store a collection of homogeneous data, while ensuring the kind of safety RAII provides. Containers are the standard answer to a very common problem: how to store a collection of homogeneous data, while ensuring the kind of safety RAII provides.
In this chapter, I won't deal with **associative containers** - which will be handled in the [very next chapter](/notebooks/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb). In this chapter, I won't deal with **associative containers** - which will be handled in the [very next chapter](/notebooks/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb).
## `std::vector` ## `std::vector`
The container of choice, which I haven't resisted using a little in previous examples so far... The container of choice, which I haven't resisted using a little in previous examples so far...
### Allocator template parameter ### Allocator template parameter
Its full prototype is: Its full prototype is:
```` ````
template template
< <
class T, class T,
class Allocator = std::allocator<T> class Allocator = std::allocator<T>
> class vector; > class vector;
```` ````
where the second template argument provides the way the memory is allocated. Most of the time the default value is ok and therefore in use you often have just the type stored within, e.g. `std::vector<double>`. where the second template argument provides the way the memory is allocated. Most of the time the default value is ok and therefore in use you often have just the type stored within, e.g. `std::vector<double>`.
### Most used constructors ### Most used constructors
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Empty constructors: no element inside. * Empty constructors: no element inside.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
std::vector<double> bar; std::vector<double> bar;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Constructors with default number of elements. The elements are the default-constructed ones in this case. * Constructors with default number of elements. The elements are the default-constructed ones in this case.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<double> bar(3); std::vector<double> bar(3);
for (auto item : bar) for (auto item : bar)
std::cout << item << std::endl; std::cout << item << std::endl;
} }
``` ```
%% Output
0
0
0
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Constructors with default number of elements and a default value. * Constructors with default number of elements and a default value.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<double> bar(3, 4.3); std::vector<double> bar(3, 4.3);
for (auto item : bar) for (auto item : bar)
std::cout << item << std::endl; std::cout << item << std::endl;
} }
``` ```
%% Output
4.3
4.3
4.3
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Since C++ 11, constructor with the initial content. * Since C++ 11, constructor with the initial content.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
for (auto item : foo) for (auto item : foo)
std::cout << item << std::endl; std::cout << item << std::endl;
} }
``` ```
%% Output
3
5
6
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* And of course copy and move constructions * And of course copy (and move) constructions
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::vector<int> bar { foo }; std::vector<int> bar { foo };
for (auto item : bar) for (auto item : bar)
std::cout << item << std::endl; std::cout << item << std::endl;
} }
``` ```
%% Output
3
5
6
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Size ### Size
A useful perk is that in true object paradigm, `std::vector` knows its size at every moment (in C with dynamic arrays you needed to keep track of the size independantly: the array was actually a pointer which indicates where the array started, but absolutely not when it ended.). A useful perk is that in true object paradigm, `std::vector` knows its size at every moment (in C with dynamic arrays you needed to keep track of the size independantly: the array was actually a pointer which indicates where the array started, but absolutely not when it ended.).
The method to know it is `size()`: The method to know it is `size()`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::cout << "Size = " << foo.size() << std::endl; std::cout << "Size = " << foo.size() << std::endl;
} }
``` ```
%% Output
Size = 3
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Adding new elements ### Adding new elements
`std::vector` provides an easy and (most of the time) cheap to add an element **at the end of the array**. The method to add a new element is `push_back`: `std::vector` provides an easy and (most of the time) cheap way to add an element **at the end of the array**. The method to add a new element is `push_back`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::cout << "Size = " << foo.size() << std::endl; std::cout << "Size = " << foo.size() << std::endl;
foo.push_back(7); foo.push_back(7);
std::cout << "Size = " << foo.size() << std::endl; std::cout << "Size = " << foo.size() << std::endl;
} }
``` ```
%% Output
Size = 3
Size = 4
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There is also an `insert()` method to add an element anywhere, but it is not very efficient (see capacity below). There is also an `insert()` method to add an element anywhere, but it is not very efficient (see capacity below).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Direct access: `operator[]` and `at()` ### Direct access: `operator[]` and `at()`
`std::vector` provides a direct access to an element through an index (that is not true for all containers) with the `operator[]`: `std::vector` provides a direct access to an element through an index (that is not true for all containers) with the `operator[]`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::cout << "foo[1] = " << foo[1] << std::endl; std::cout << "foo[1] = " << foo[1] << std::endl; // Remember: indexing starts at 0 in C and C++
} }
``` ```
%% Output
foo[1] = 5
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Direct access is not checked: if you go beyond the size of the vector you enter undefined behaviour territory: Direct access is not checked: if you go beyond the size of the vector you enter undefined behaviour territory:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::cout << "foo[4] = " << foo[4] << std::endl; // undefined territory std::cout << "foo[4] = " << foo[4] << std::endl; // undefined territory
} }
``` ```
%% Output
foo[4] = 0
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A specific method `at()` exists that performs the adequate check and thrown an exception if needed: A specific method `at()` exists that performs the adequate check and thrown an exception if needed:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> foo { 3, 5, 6 }; std::vector<int> foo { 3, 5, 6 };
std::cout << "foo[4] = " << foo.at(4) << std::endl; // exception thrown std::cout << "foo[4] = " << foo.at(4) << std::endl; // exception thrown
} }
``` ```
%% Output
foo[4] =
Standard Exception: vector
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
I do not necessarily recommend it: I would rather check the index is correct with an `assert`, which provides the runtime check in debug mode only and doesn't slow down the code in release mode. I do not necessarily recommend it: I would rather check the index is correct with an `assert`, which provides the runtime check in debug mode only and doesn't slow down the code in release mode.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Under the hood: storage and capacity ### Under the hood: storage and capacity
In practice, `std::vector` is a dynamic array allocated with safety through the use of RAII. In practice, `std::vector` is a dynamic array allocated with safety through the use of RAII.
To make `push_back` a O(1) operation most of the time, slightly more memory than what you want to use is allocated. To make `push_back` a O(1) operation most of the time, slightly more memory than what you want to use is allocated.
The `capacity()` must not be mistaken for the `size()`: The `capacity()` must not be mistaken for the `size()`:
* `size()` is the number of elements in the array and might be of use for the end-user. * `size()` is the number of elements in the array and might be of use for the end-user.
* `capacity()` is more internal: it is the underlying memory the compiler allocated for the container, which is a bit larger to make room for few new elements. * `capacity()` is more internal: it is the underlying memory the compiler allocated for the container, which is a bit larger to make room for few new elements.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<std::size_t> foo; std::vector<std::size_t> foo;
for (auto i = 0ul; i < 10ul; ++i) for (auto i = 0ul; i < 10ul; ++i)
{ {
std::cout << "Vector: size = " << foo.size() << " and capacity = " << foo.capacity() << std::endl; std::cout << "Vector: size = " << foo.size() << " and capacity = " << foo.capacity() << std::endl;
foo.push_back(i); foo.push_back(i);
} }
} }
``` ```
%% Output
Vector: size = 0 and capacity = 0
Vector: size = 1 and capacity = 1
Vector: size = 2 and capacity = 2
Vector: size = 3 and capacity = 4
Vector: size = 4 and capacity = 4
Vector: size = 5 and capacity = 8
Vector: size = 6 and capacity = 8
Vector: size = 7 and capacity = 8
Vector: size = 8 and capacity = 8
Vector: size = 9 and capacity = 16
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The pattern for capacity is clear here but is not dictated by the standard: it is up to the STL vendor to choose the way it deals with it. The pattern for capacity is clear here but is not dictated by the standard: it is up to the STL vendor to choose the way it deals with it.
So what's happen when the capacity is reached and a new element is added? So what's happen when the capacity is reached and a new element is added?
* A new dynamic array with the new capacity is created. * A new dynamic array with the new capacity is created.
* Each element of the former dynamic array is **copied** (or eventually **moved**) into the new one. * Each element of the former dynamic array is **copied** (or eventually **moved**) into the new one.
* The former dynamic array is destroyed. * The former dynamic array is destroyed.
The least we can say is we're far from O(1) here! (and we're with a POD type - copy is cheap, which is not the case for certain types of objects...) So obviously it is better to avoid this operation as much as possible! The least we can say is we're far from O(1) here! (and we're with a POD type - copy is cheap, which is not the case for certain types of objects...) So obviously it is better to avoid this operation as much as possible!
### `reserve()` and `resize()` ### `reserve()` and `resize()`
`reserve()` is the method to set manually the size of the capacity. When you have a clue of the expected number of elements, it is better to provide it: even if your guess was flawed, it limits the number of reallocations: `reserve()` is the method to set manually the value of the capacity. When you have a clue of the expected number of elements, it is better to provide it: even if your guess was flawed, it limits the number of reallocations:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<std::size_t> foo; std::vector<std::size_t> foo;
foo.reserve(5); // 10 would have been better of course! foo.reserve(5); // 10 would have been better of course!
for (auto i = 0ul; i < 10ul; ++i) for (auto i = 0ul; i < 10ul; ++i)
{ {
std::cout << "Vector: size = " << foo.size() << " and capacity = " << foo.capacity() << std::endl; std::cout << "Vector: size = " << foo.size() << " and capacity = " << foo.capacity() << std::endl;
foo.push_back(i); foo.push_back(i);
} }
} }
``` ```
%% Output
Vector: size = 0 and capacity = 5
Vector: size = 1 and capacity = 5
Vector: size = 2 and capacity = 5
Vector: size = 3 and capacity = 5
Vector: size = 4 and capacity = 5
Vector: size = 5 and capacity = 5
Vector: size = 6 and capacity = 10
Vector: size = 7 and capacity = 10
Vector: size = 8 and capacity = 10
Vector: size = 9 and capacity = 10
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It must not be mistaken with `resize()`, which changes the size of the meaningful content of the dynamic array. It must not be mistaken with `resize()`, which changes the size of the meaningful content of the dynamic array.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
// Utility to print the content of a non-associative container. // Utility to print the content of a non-associative container.
// Don't bother with it now: it uses up iterators we'll see a bit below. // Don't bother with it now: it uses up iterators we'll see a bit below.
template template
< <
class VectorT class VectorT
> >
void PrintVector(const VectorT& vector, void PrintVector(const VectorT& vector,
std::string separator = ", ", std::string opener = "[", std::string closer = "]\n") std::string separator = ", ", std::string opener = "[", std::string closer = "]\n")
{ {
auto size = vector.size(); auto size = vector.size();
std::cout << "Size = " << size << " Capacity = " << vector.capacity() << " Content = "; std::cout << "Size = " << size << " Capacity = " << vector.capacity() << " Content = ";
std::cout << opener; std::cout << opener;
auto it = vector.cbegin(); auto it = vector.cbegin();
auto end = vector.cend(); auto end = vector.cend();
static_cast<void>(end); // to avoid compilation warning in release mode static_cast<void>(end); // to avoid compilation warning in release mode
for (decltype(size) i = 0u; i + 1u < size; ++it, ++i) for (decltype(size) i = 0u; i + 1u < size; ++it, ++i)
{ {
assert(it != end); assert(it != end);
std::cout << *it << separator; std::cout << *it << separator;
} }
if (size > 0u) if (size > 0u)
std::cout << *it; std::cout << *it;
std::cout << closer; std::cout << closer;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<std::size_t> foo { 3, 5}; std::vector<std::size_t> foo { 3, 5};
PrintVector(foo); PrintVector(foo);
foo.resize(8, 10); // Second optional argument gives the values to add. foo.resize(8, 10); // Second optional argument gives the values to add.
PrintVector(foo); PrintVector(foo);
foo.resize(3, 15); foo.resize(3, 15);
PrintVector(foo); PrintVector(foo);
} }
``` ```
%% Output
Size = 2 Capacity = 2 Content = [3, 5]
Size = 8 Capacity = 8 Content = [3, 5, 10, 10, 10, 10, 10, 10]
Size = 3 Capacity = 8 Content = [3, 5, 10]
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you see, `resize()` may increase or decrease the size of the `std::vector`; if it decreases it some values are lost. As you see, `resize()` may increase or decrease the size of the `std::vector`; if it decreases it some values are lost.
You may see as well the capacity is not adapted consequently; you may use `shrink_to_fit()` method to tell the program to reduce the capacity but it is not binding and the compiler may not do so (it does here): You may see as well the capacity is not adapted consequently; you may use `shrink_to_fit()` method to tell the program to reduce the capacity but it is not binding and the compiler may not do so (it does here):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<std::size_t> foo { 3, 5}; std::vector<std::size_t> foo { 3, 5};
PrintVector(foo); PrintVector(foo);
foo.resize(8, 10); // Second optional argument gives the values to add. foo.resize(8, 10); // Second optional argument gives the values to add.
PrintVector(foo); PrintVector(foo);
foo.resize(3, 10); foo.resize(3, 10);
PrintVector(foo); PrintVector(foo);
foo.shrink_to_fit(); foo.shrink_to_fit();
PrintVector(foo); PrintVector(foo);
} }
``` ```
%% Output
Size = 2 Capacity = 2 Content = [3, 5]
Size = 8 Capacity = 8 Content = [3, 5, 10, 10, 10, 10, 10, 10]
Size = 3 Capacity = 8 Content = [3, 5, 10]
Size = 3 Capacity = 3 Content = [3, 5, 10]
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As a rule: As a rule:
* When you use `reserve`, it often means you intend to add new content with `push_back()` which increases the size by 1 (and the capacity would be unchanged provided you estimated the argument given to reserve well). * When you use `reserve`, it often means you intend to add new content with `push_back()` which increases the size by 1 (and the capacity would be unchanged provided you estimated the argument given to reserve well).
* When you use `resize`, you intend to modify on the spot the values in the container, with for instance `operator[]`, a loop or iterators. * When you use `resize`, you intend to modify on the spot the values in the container, with for instance `operator[]`, a loop or iterators.
A common mistake is to mix up unduly both: A common mistake is to mix up unduly both:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> five_pi_digits; std::vector<int> five_pi_digits;
five_pi_digits.resize(5); five_pi_digits.resize(5);
five_pi_digits.push_back(3); five_pi_digits.push_back(3);
five_pi_digits.push_back(1); five_pi_digits.push_back(1);
five_pi_digits.push_back(4); five_pi_digits.push_back(4);
five_pi_digits.push_back(1); five_pi_digits.push_back(1);
five_pi_digits.push_back(5); five_pi_digits.push_back(5);
for (auto item : five_pi_digits) for (auto item : five_pi_digits)
std::cout << "Digit = " << item << std::endl; // Not what we intended! std::cout << "Digit = " << item << std::endl; // Not what we intended!
} }
``` ```
%% Output
Digit = 0
Digit = 0
Digit = 0
Digit = 0
Digit = 0
Digit = 3
Digit = 1
Digit = 4
Digit = 1
Digit = 5
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std::vector` as a C array ### `std::vector` as a C array
In your code, you might at some point use a C library which deals with dynamic array. If the function doesn't mess with the structure of the dynamic array (by reallocating the content for instance), you may use without any issue a `std::vector` through its method `data()` In your code, you might at some point use a C library which deals with dynamic array. If the function doesn't mess with the structure of the dynamic array (by reallocating the content for instance), you may use without any issue a `std::vector` through its method `data()`
**NOTE:** Xeus-cling does not support yet C printf; so you may try this [@Coliru](https://coliru.stacked-crooked.com/a/f55f1e11833594aa). **NOTE:** Xeus-cling does not support yet C printf; so you may try this [@Coliru](https://coliru.stacked-crooked.com/a/f55f1e11833594aa).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <cstdio> #include <cstdio>
// A C function // A C function
void C_PrintArray(double* array, std::size_t Nelt) void C_PrintArray(double* array, std::size_t Nelt)
{ {
if (Nelt > 0ul) if (Nelt > 0ul)
{ {
printf("["); printf("[");
for (auto i = 0ul; i < Nelt - 1; ++i) for (auto i = 0ul; i < Nelt - 1; ++i)
printf("%lf, ", array[i]); printf("%lf, ", array[i]);
printf("%lf]", array[Nelt - 1]); printf("%lf]", array[Nelt - 1]);
} }
else else
printf("[]"); printf("[]");
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 }; std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 };
C_PrintArray(cpp_vector.data(), cpp_vector.size()); C_PrintArray(cpp_vector.data(), cpp_vector.size());
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`data()` was introduced in C++ 11; previously you could do the same with equivalent but much less appealing call to the address of the first element: `data()` was introduced in C++ 11; previously you could do the same with equivalent but much less appealing call to the address of the first element:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 }; std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 };
C_PrintArray(&cpp_vector[0], cpp_vector.size()); C_PrintArray(&cpp_vector[0], cpp_vector.size());
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Iterators ### Iterators
**Iterators** are an useful feature that is less prominent with C++ 11 (albeit still very useful if you use STL algorithm) but needs to be at least acknowledged as under the hood they are still used in the more sexy [`for`](/notebooks/1-ProceduralProgramming/2-Conditions-and-loops.ipynb#New-for-loop) loops now available. **Iterators** are an useful feature that is less prominent with C++ 11 (albeit still very useful if you use STL algorithm) but needs to be at least acknowledged as under the hood they are still used in the more sexy [`for`](/notebooks/1-ProceduralProgramming/2-Conditions-and-loops.ipynb#New-for-loop) loops now available.
The idea of an iterator is to provide an object to navigate over all (or part of the) items of a container efficiently. The idea of an iterator is to provide an object to navigate over all (or part of the) items of a container efficiently.
Let's forget for a while our syntactic sugar `for (auto item : container)` and see what our options are: Let's forget for a while our syntactic sugar `for (auto item : container)` and see what our options are:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 }; std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 };
const auto size = cpp_vector.size(); const auto size = cpp_vector.size();
for (auto i = 0ul; i < size; ++i) for (auto i = 0ul; i < size; ++i)
std::cout << cpp_vector[i] << " "; std::cout << cpp_vector[i] << " ";
} }
``` ```
%% Output
3 8 9 -12.3 -32.35
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is not extremely efficient: at each call to `operator[]`, the program must figure out the element to draw without using the fact it had just fetched the element just in the previous memory location. Iterators provides this more efficient access: It may not extremely efficient: at each call to `operator[]`, the program must figure out the element to draw without using the fact it had just fetched the element just in the previous memory location (in practice now compilers are rather smart and figure this out...) Iterators provides this more efficient access:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 }; std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 };
std::vector<double>::const_iterator end = cpp_vector.cend(); std::vector<double>::const_iterator end = cpp_vector.cend();
for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; ++it) for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; ++it)
std::cout << it - cpp_vector.cbegin() << "\t" << *it << " "; std::cout << it - cpp_vector.cbegin() << "\t" << *it << " ";
} }
``` ```
%% Output
0 3 1 8 2 9 3 -12.3 4 -32.35
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is more efficient and quite verbosy; prior to C++ 11 you had to use this nonetheless (just `auto` would simplify greatly the syntax here but it is also a C++11 addition...) It is more efficient and quite verbosy; prior to C++ 11 you had to use this nonetheless (just `auto` would simplify greatly the syntax here but it is also a C++11 addition...)
Iterators are *not* pointers, even if they behave really similarly, e.g. they may use the same `*` and `->` syntax (they might be implemented as pointers, but think of it as private inheritance in this case...) Iterators are *not* pointers, even if they behave really similarly, e.g. they may use the same `*` and `->` syntax (they might be implemented as pointers, but think of it as private inheritance in this case...)
There are several flavors: There are several flavors:
* Constant iterators, used here, with which you can only read the value under the iterator. * Constant iterators, used here, with which you can only read the value under the iterator.
* Iterators, with which you can also modify it. * Iterators, with which you can also modify it.
* Reverse iterators, to iterate the container from the last to the first. * Reverse iterators, to iterate the container from the last to the first (avoid them if you can: they are a bit messy to use and may not be used in every algorithms in which standard iterators are ok...)
There are default values for each container: There are default values for each container:
* `begin()` points to the very first element of the container. * `begin()` points to the very first element of the container.
* `end()` is **after** the last element of the container. * `end()` is **after** the last element of the container.
* `cbegin()` is the constant_iterator that does the same job as `begin()`; prior to C++11 it was confusingly named `begin()`. * `cbegin()` is the constant_iterator that does the same job as `begin()`; prior to C++11 it was confusingly named `begin()`.
* `cend()`: you might probably figure it out... * `cend()`: you might probably figure it out...
* `rbegin()` points to the very last element of the container. * `rbegin()` points to the very last element of the container.
* `rend()` is **before** the first element of the container. * `rend()` is **before** the first element of the container.
What is tricky with them is that they may become invalid if some operations are performed in the time being on the container. For instance if the container is extended the iterators become invalid. Therefore, code like: What is tricky with them is that they may become invalid if some operations are performed in the time being on the container. For instance if the container is extended the iterators become invalid. Therefore, code like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
{ {
std::vector<int> vec { 2, 3, 4, 5, 7, 18 }; std::vector<int> vec { 2, 3, 4, 5, 7, 18 };
for (auto item : vec) for (auto item : vec)
{ {
if (item % 2 == 0) if (item % 2 == 0)
vec.push_back(item + 2); // don't do that! vec.push_back(item + 2); // don't do that!
} }
PrintVector(vec); PrintVector(vec);
} }
``` ```
%% Output
Size = 9 Capacity = 12 Content = [2, 3, 4, 5, 7, 18, 4, 6, 20]
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
is undefined behaviour: it might work (seems to in this notebook) but is not robust. Here it "works" but you may see the iteration is done over the initial vector; the additional values aren't iterated over (we would end up with an infinite loop in this case). is undefined behaviour: it might work (seems to in this notebook) but is not robust. Here it "works" but you may see the iteration is done over the initial vector; the additional values aren't iterated over (we would end up with an infinite loop in this case).
So, the bottom line is you should really separate actions that modify the structure of a container and iteration over it. So, the bottom line is you should really separate actions that modify the structure of a container and iteration over it.
## Incrementing / decrementing iterators ## Incrementing / decrementing iterators
As you POD types, there are both a pre- and post-increment available: As you POD types, there are both a pre- and post-increment available:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 }; std::vector<double> cpp_vector { 3., 8., 9., -12.3, -32.35 };
std::vector<double>::const_iterator end = cpp_vector.cend(); std::vector<double>::const_iterator end = cpp_vector.cend();
for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; ++it) // pre-increment for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; ++it) // pre-increment
std::cout << *it << " "; std::cout << *it << " ";
std::cout << std::endl; std::cout << std::endl;
for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; it++) // post-increment for (std::vector<double>::const_iterator it = cpp_vector.cbegin(); it != end; it++) // post-increment
std::cout << *it << " "; std::cout << *it << " ";
} }
``` ```
%% Output
3 8 9 -12.3 -32.35
3 8 9 -12.3 -32.35
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Without any surprises the result is the same... but the efficiency is absolutely not: post-increment actually makes a copy of the iterator that replaces the former one, whereas pre-increment one just modify the current value. So if you do not care about pre- or post- increment (as in the case above) stick with pre-increment one. Without any surprises the result is the same... but the efficiency is absolutely not: post-increment actually makes a copy of the iterator that replaces the former one, whereas pre-increment one just modify the current value. So if you do not care about pre- or post- increment (as in the case above) stick with pre-increment one.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Other containers ## Other containers
`std::vector` is not the only possible choice; I will present very briefly the other possibilities here: `std::vector` is not the only possible choice; I will present very briefly the other possibilities here:
* `std::list`: A double-linked list: the idea is that each element knows the address of the element before and the element after. It might be considered if you need to add often elements at specific locations in the list: adding a new element is just changing 2 pointers and setting 2 new ones for instances. You can't access directly an element by its index with a `std::list`. * `std::list`: A double-linked list: the idea is that each element knows the addresses of the element before and the element after. It might be considered if you need to add often elements at specific locations in the list: adding a new element is just changing 2 pointers and setting 2 new ones. You can't access directly an element by its index with a `std::list`.
* `std::slist`: A single-linked list: similar as a `std::list` except only the pointer to the next element is kept. * `std::slist`: A single-linked list: similar as a `std::list` except only the pointer to the next element is kept.
* `std::deque`: For "double ended queue"; this container may be helpful if you need to store a really huge amount of data that might not fit within a `std::vector`. It might also be of use if you are to add often elements in front on the list: there is `push_front()` method as well as a `push_back` one. Item 18 of \cite{Meyers2001} recommends using `std::deque` with `bool`: `std::vector<bool>` was an experiment to provide a specific implementation to spare memory that went wrong... * `std::deque`: For "double ended queue"; this container may be helpful if you need to store a really huge amount of data that might not fit within a `std::vector`. It might also be of use if you are to add often elements in front on the list: there is `push_front()` method as well as a `push_back` one. Item 18 of \cite{Meyers2001} recommends using `std::deque` with `bool`: `std::vector<bool>` was an experiment to provide a specific implementation to spare memory that went wrong and should therefore be avoided...
* `std::array`: You should use this one if the number of elements is known at compile time and doesn't change at all. * `std::array`: You should use this one if the number of elements is known at compile time and doesn't change at all.
* `std::string`: Yes, it is actually a container! I will not tell much more about it; just add that it is the sole container besides `std::vector` that ensures continuous storage. * `std::string`: Yes, it is actually a container! I will not tell much more about it; just add that it is the sole container besides `std::vector` that ensures continuous storage.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # References
(<a id="cit-Meyers2001" href="#call-Meyers2001">Meyers, 2001</a>) Scott Meyers, ``_Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library_'', 2001. (<a id="cit-Meyers2001" href="#call-Meyers2001">Meyers, 2001</a>) Scott Meyers, ``_Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library_'', 2001.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2020_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [Associative containers](/notebooks/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb) # [Getting started in C++](/) - [Useful concepts and STL](./0-main.ipynb) - [Associative containers](./4-AssociativeContainers.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#std::map" data-toc-modified-id="std::map-2"><code>std::map</code></a></span><ul class="toc-item"><li><span><a href="#Construction" data-toc-modified-id="Construction-2.1">Construction</a></span></li><li><span><a href="#Iteration" data-toc-modified-id="Iteration-2.2">Iteration</a></span></li><li><span><a href="#Provide-another-ordering-rule" data-toc-modified-id="Provide-another-ordering-rule-2.3">Provide another ordering rule</a></span></li><li><span><a href="#insert()" data-toc-modified-id="insert()-2.4">insert()</a></span></li><li><span><a href="#Access-to-one-element:-don't-use-operator[]!" data-toc-modified-id="Access-to-one-element:-don't-use-operator[]!-2.5">Access to one element: don't use <code>operator[]</code>!</a></span></li><li><span><a href="#Unicity-of-key" data-toc-modified-id="Unicity-of-key-2.6">Unicity of key</a></span></li><li><span><a href="#Using-objects-as-keys" data-toc-modified-id="Using-objects-as-keys-2.7">Using objects as keys</a></span></li></ul></li><li><span><a href="#std::set" data-toc-modified-id="std::set-3"><code>std::set</code></a></span></li><li><span><a href="#std::unordered_map" data-toc-modified-id="std::unordered_map-4">std::unordered_map</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#std::map" data-toc-modified-id="std::map-2"><code>std::map</code></a></span><ul class="toc-item"><li><span><a href="#Construction" data-toc-modified-id="Construction-2.1">Construction</a></span></li><li><span><a href="#Iteration" data-toc-modified-id="Iteration-2.2">Iteration</a></span></li><li><span><a href="#Provide-another-ordering-rule" data-toc-modified-id="Provide-another-ordering-rule-2.3">Provide another ordering rule</a></span></li><li><span><a href="#insert()" data-toc-modified-id="insert()-2.4">insert()</a></span></li><li><span><a href="#Access-to-one-element:-don't-use-operator[]!" data-toc-modified-id="Access-to-one-element:-don't-use-operator[]!-2.5">Access to one element: don't use <code>operator[]</code>!</a></span></li><li><span><a href="#Unicity-of-key" data-toc-modified-id="Unicity-of-key-2.6">Unicity of key</a></span></li><li><span><a href="#Using-objects-as-keys" data-toc-modified-id="Using-objects-as-keys-2.7">Using objects as keys</a></span></li></ul></li><li><span><a href="#std::set" data-toc-modified-id="std::set-3"><code>std::set</code></a></span></li><li><span><a href="#std::unordered_map" data-toc-modified-id="std::unordered_map-4">std::unordered_map</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
A `std::vector` can be seen as an association between two types: A `std::vector` can be seen as an association between two types:
* A `std::size_t` * A `std::size_t`
index, which value is in interval [0, size[, that acts as a key. index, which value is in interval [0, size[, that acts as a key.
* The value actually stored. * The value actually stored.
The `operator[]` might be used to access one of them: The `operator[]` might be used to access one of them:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<int> prime { 2, 3, 5, 7, 11, 13, 17, 19 }; std::vector<int> prime { 2, 3, 5, 7, 11, 13, 17, 19 };
auto index = 3ul; auto index = 3ul;
std::cout << "Element which key is " << index << " is " << prime[index] << std::endl; std::cout << "Element which key is " << index << " is " << prime[index] << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
An associative container is an extension: what if we could loosen the constraint upon the key and use something else? An associative container is an extension: what if we could loosen the constraint upon the key and use something else?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `std::map` ## `std::map`
### Construction ### Construction
`std::map` is a list of key/value pairs that is ordered through a relationship imposed on the keys. `std::map` is a list of key/value pairs that is ordered through a relationship imposed on the keys.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
auto index = "Charlie"; auto index = "Charlie";
std::cout << "Element which key is " << index << " is " << age_list[index] << std::endl; std::cout << "Element which key is " << index << " is " << age_list[index] << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Iteration ### Iteration
In this example, we set three people with their age. We may iterate through it; the actual storage of an item is here a `std::pair<std::string, unsigned int>`. We haven't seen `std::pair` so far, but think of it as a `std::tuple` with 2 elements (it existed prior to the tuple in fact). In this example, we set three people with their age. We may iterate through it; the actual storage of an item is here a `std::pair<std::string, unsigned int>`. We haven't seen `std::pair` so far, but think of it as a `std::tuple` with 2 elements (it existed prior to the tuple in fact).
There are two handy attributes to access the respective first and second element: `first` and `second`. There are two handy attributes to access the respective first and second element: `first` and `second`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Provide another ordering rule ### Provide another ordering rule
The output order is not an accident: as I said it is an **ordered** associative container, and the key must provide a relationship. The default one is `std::less` but you might specify another in template arguments: The output order is not an accident: as I said it is an **ordered** associative container, and the key must provide a relationship. The default one is `std::less` but you might specify another in template arguments:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int, std::greater<std::string>> age_list std::map<std::string, unsigned int, std::greater<std::string>> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### insert() ### insert()
You may insert another element later with `insert()`: You may insert another element later with `insert()`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
age_list.insert({"Dave", 44}); age_list.insert({"Dave", 44});
age_list.insert({"Alice", 32}); age_list.insert({"Alice", 32});
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
See here that David was correctly inserted... but Alice was unchanged! See here that David was correctly inserted... but Alice was unchanged!
In fact `insert` returns a pair: In fact `insert` returns a pair:
* First is an iterator to the newly inserted element, or to the position of the one that made the insertion fail. * First is an iterator to the newly inserted element, or to the position of the one that made the insertion fail.
* Second is a boolean that returns `true` if the insertion worked. * Second is a boolean that returns `true` if the insertion worked.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
{ {
auto result = age_list.insert({"Dave", 44}); auto result = age_list.insert({"Dave", 44});
if (!result.second) if (!result.second)
std::cerr << "Insertion of Dave failed" << std::endl; std::cerr << "Insertion of Dave failed" << std::endl;
} }
{ {
auto result = age_list.insert({"Alice", 32}); auto result = age_list.insert({"Alice", 32});
if (!result.second) if (!result.second)
std::cerr << "Insertion of Alice failed" << std::endl; std::cerr << "Insertion of Alice failed" << std::endl;
} }
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
That's somewhere I dislike in this very useful class: error handling is not up to my taste... That's something I dislike in this very useful class: error handling is not up to my taste as you have to remember to check explicitly all went right...
### Access to one element: don't use `operator[]`! ### Access to one element: don't use `operator[]`!
And this is not the sole example: let's look for an element in a map: And this is not the sole example: let's look for an element in a map:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
std::cout << "Alice : " << age_list["Alice"] << std::endl; std::cout << "Alice : " << age_list["Alice"] << std::endl;
std::cout << "Erin : " << age_list["Erin"] << std::endl; std::cout << "Erin : " << age_list["Erin"] << std::endl;
std::cout << "========" << std::endl; std::cout << "========" << std::endl;
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So if you provide a wrong key; it doesn't yell and create on the spot a new entry, filling the associated value with the default constructor for the type... So if you provide a wrong key, it doesn't yell and create on the spot a new entry, filling the associated value with the default constructor for the type...
To do it properly (but more verbose!), use the `find()` method (if you're intrigued by the use of iterator there, we will present them more in details in the notebook about [algorithms](/notebooks/5-UsefulConceptsAndSTL/7-Algorithms.ipynb)): To do it properly (but more verbose!), use the `find()` method (if you're intrigued by the use of iterator there, we will present them more in details in the notebook about [algorithms](./7-Algorithms.ipynb)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <map> #include <map>
#include <iostream> #include <iostream>
{ {
std::map<std::string, unsigned int> age_list std::map<std::string, unsigned int> age_list
{ {
{ "Alice", 25 }, { "Alice", 25 },
{ "Charlie", 31 }, { "Charlie", 31 },
{ "Bob", 22 }, { "Bob", 22 },
}; };
auto it = age_list.find("Alice"); auto it = age_list.find("Alice");
if (it == age_list.cend()) if (it == age_list.cend())
std::cerr << "No Alice found in the listing!" << std::endl; std::cerr << "No Alice found in the listing!" << std::endl;
else else
std::cout << "Alice's age is " << it->second << std::endl; std::cout << "Alice's age is " << it->second << std::endl;
it = age_list.find("Erin"); it = age_list.find("Erin");
if (it == age_list.cend()) if (it == age_list.cend())
std::cerr << "No Erin found in the listing!" << std::endl; std::cerr << "No Erin found in the listing!" << std::endl;
else else
std::cout << "Erin's age is " << it->second << std::endl; std::cout << "Erin's age is " << it->second << std::endl;
for (const auto& pair : age_list) for (const auto& pair : age_list)
std::cout << pair.first << " : " << pair.second << std::endl; std::cout << pair.first << " : " << pair.second << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A side note which will be useful to explain later the `std::unordered_map`: search is performed by dichotomy (~O(log N)). A side note which will be useful to explain later the `std::unordered_map`: search is performed by dichotomy (~O(log N)).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Unicity of key ### Unicity of key
`std::map` is built on the fact a key must be unique. `std::map` is built on the fact a key must be unique.
If you need to enable possible repetition of keys, you should look at `std::multimap` which provides this possibility with slightly different interface (rather obviously `find()` is replaced by methods that returns a range of iterators). If you need to enable possible repetition of keys, you should look at `std::multimap` which provides this possibility with slightly different interface (rather obviously `find()` is replaced by methods that returns a range of iterators).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Using objects as keys ### Using objects as keys
You may use your own objects as keys, provided that: You may use your own objects as keys, provided that:
* Either you define `operator<` for it. `operator==` doesn't matter: even in `find` it is really `operator<` that is used! * Either you define `operator<` for it. `operator==` doesn't matter: even in `find` it is really `operator<` that is used!
* Or provide as template parameter the ordering relationship you intend to use. * Or provide as template parameter the ordering relationship you intend to use.
**WARNING:** If you're using pointers as keys, make sure to provide an adequate relationship ordering, typically that takes the pointed object relationship. Otherwise from one run to another you might end with different results as the address won't probably be given in the same order... **WARNING:** If you're using pointers as keys, make sure to provide an adequate relationship ordering, typically that takes the pointed object relationship. Otherwise from one run to another you might end with different results as the address won't probably be given in the same order...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `std::set` ## `std::set`
`std::set` is a special case in which you do not associate a value to the key. The interface is roughly the same. `std::set` is a special case in which you do not associate a value to the key. The interface is roughly the same.
It might be used for instance if you want to keep a list of stuff you have encountered at least once: you don't care about how many times, but you want to know if it was encountered at least once. A `std::vector` would be inappropriate: you would have to look up its whole content before each insertion. With a `std::set` it is already built-in in the class. It might be used for instance if you want to keep a list of stuff you have encountered at least once: you don't care about how many times, but you want to know if it was encountered at least once. A `std::vector` would be inappropriate: you would have to look up its whole content before each insertion. With a `std::set` it is already built-in in the class.
## std::unordered_map ## std::unordered_map
This is another associative container introduced in C++ 11, with a different trade-off (and closer to a `dict` in Python for instance): This is another associative container introduced in C++ 11, with a different trade-off (and closer to a `dict` in Python for instance):
* Access is much more efficient (~O(1), i.e. independant on the number of elements!) * Access is much more efficient (~O(1), i.e. independant on the number of elements!)
* Memory imprint is bigger. * Memory imprint is bigger.
* Adding new elements is more expensive. * Adding new elements is more expensive.
* The result is not ordered, and there are no rules whatsoever: two runs might not yield the list in the same order. * The result is not ordered, and there are no rules whatsoever: two runs might not yield the list in the same order.
The constraint of the key is different too: the key must be **hashable**: there must be a specialization of `std::hash` for the type used for key. It must also define `operator==`. The constraint of the key is different too: the key must be **hashable**: there must be a specialization of `std::hash` for the type used for key. It must also define `operator==`.
STL provides good such **hash tables** for POD types (and few others like `std::string`); it is not trivial (but still possible - see for instance \cite{Josuttis2012} for a discussion on this topic) to add new ones. STL provides good such **hash tables** for POD types (and few others like `std::string`); it is not trivial (but still possible - see for instance \cite{Josuttis2012} for a discussion on this topic) to add new ones.
So to put in a nutshell, if your key type is already handled by the STL and you read data more often than inserting new ones, you should really use this type. So to put in a nutshell, if your key type is already handled by the STL and you read data more often than inserting new ones, you should really use this type.
Just an additional note: \cite{Josuttis2012} recommends changing the default internal setting of the class for efficiency: there is an internal float value named `max_load_factor` which default value is 1; API of the class introduce a mutator to modify it. He says 0.7f or 0.8f is more efficient. Just an additional note: \cite{Josuttis2012} recommends changing the default internal setting of the class for efficiency: there is an internal float value named `max_load_factor` which default value is 1; API of the class introduce a mutator to modify it. He says 0.7f or 0.8f is more efficient.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <unordered_map> #include <unordered_map>
{ {
std::unordered_map<int, double> list; std::unordered_map<int, double> list;
list.max_load_factor(0.7f); list.max_load_factor(0.7f);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # References
[<a id="cit-Josuttis2012" href="#call-Josuttis2012">Josuttis2012</a>] Nicolai M. Josuttis, ``_The C++ Standard Library: A Tutorial and Reference_'', 2012. [<a id="cit-Josuttis2012" href="#call-Josuttis2012">Josuttis2012</a>] Nicolai M. Josuttis, ``_The C++ Standard Library: A Tutorial and Reference_'', 2012.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2020_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [Move semantics](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) # [Getting started in C++](/) - [Useful concepts and STL](./0-main.ipynb) - [Move semantics](./5-MoveSemantics.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Motivation:-eliminate-unnecessary-deep-copies" data-toc-modified-id="Motivation:-eliminate-unnecessary-deep-copies-1">Motivation: eliminate unnecessary deep copies</a></span></li><li><span><a href="#A-traditional-answer:-to-allow-the-exchange-of-internal-data" data-toc-modified-id="A-traditional-answer:-to-allow-the-exchange-of-internal-data-2">A traditional answer: to allow the exchange of internal data</a></span></li><li><span><a href="#Reminder-on-references-in-C++03" data-toc-modified-id="Reminder-on-references-in-C++03-3">Reminder on references in C++03</a></span></li><li><span><a href="#C++11/14-:-temporary-references" data-toc-modified-id="C++11/14-:-temporary-references-4">C++11/14 : temporary references</a></span></li><li><span><a href="#Function-with-r-value-arguments" data-toc-modified-id="Function-with-r-value-arguments-5">Function with r-value arguments</a></span></li><li><span><a href="#std::move" data-toc-modified-id="std::move-6"><code>std::move</code></a></span></li><li><span><a href="#Return-value-optimization-(RVO)-and-copy-elision" data-toc-modified-id="Return-value-optimization-(RVO)-and-copy-elision-7">Return value optimization (RVO) and copy elision</a></span></li><li><span><a href="#Move-constructors" data-toc-modified-id="Move-constructors-8">Move constructors</a></span></li><li><span><a href="#Temporary-reference-argument-within-a-function" data-toc-modified-id="Temporary-reference-argument-within-a-function-9">Temporary reference argument within a function</a></span></li><li><span><a href="#Move-semantics-in-the-STL" data-toc-modified-id="Move-semantics-in-the-STL-10">Move semantics in the STL</a></span></li><li><span><a href="#Forwarding-reference-(or-universal-reference)" data-toc-modified-id="Forwarding-reference-(or-universal-reference)-11">Forwarding reference (or universal reference)</a></span></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Motivation:-eliminate-unnecessary-deep-copies" data-toc-modified-id="Motivation:-eliminate-unnecessary-deep-copies-1">Motivation: eliminate unnecessary deep copies</a></span></li><li><span><a href="#A-traditional-answer:-to-allow-the-exchange-of-internal-data" data-toc-modified-id="A-traditional-answer:-to-allow-the-exchange-of-internal-data-2">A traditional answer: to allow the exchange of internal data</a></span></li><li><span><a href="#Reminder-on-references-in-C++03" data-toc-modified-id="Reminder-on-references-in-C++03-3">Reminder on references in C++03</a></span></li><li><span><a href="#C++11/14-:-temporary-references" data-toc-modified-id="C++11/14-:-temporary-references-4">C++11/14 : temporary references</a></span></li><li><span><a href="#Function-with-r-value-arguments" data-toc-modified-id="Function-with-r-value-arguments-5">Function with r-value arguments</a></span></li><li><span><a href="#std::move" data-toc-modified-id="std::move-6"><code>std::move</code></a></span></li><li><span><a href="#Return-value-optimization-(RVO)-and-copy-elision" data-toc-modified-id="Return-value-optimization-(RVO)-and-copy-elision-7">Return value optimization (RVO) and copy elision</a></span></li><li><span><a href="#Move-constructors" data-toc-modified-id="Move-constructors-8">Move constructors</a></span></li><li><span><a href="#Temporary-reference-argument-within-a-function" data-toc-modified-id="Temporary-reference-argument-within-a-function-9">Temporary reference argument within a function</a></span></li><li><span><a href="#Move-semantics-in-the-STL" data-toc-modified-id="Move-semantics-in-the-STL-10">Move semantics in the STL</a></span></li><li><span><a href="#Forwarding-reference-(or-universal-reference)" data-toc-modified-id="Forwarding-reference-(or-universal-reference)-11">Forwarding reference (or universal reference)</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Motivation: eliminate unnecessary deep copies ## Motivation: eliminate unnecessary deep copies
In many situations, unnecessary deep copies are made. In many situations, unnecessary deep copies are made.
In the example below, during the exchange between the two instances of the `Text` class, we have to make 3 memory deallocations, 3 allocations, 3 character copy loops... where 3 pointer copies would be sufficient. In the example below, during the exchange between the two instances of the `Text` class, we have to make 3 memory deallocations, 3 allocations, 3 character copy loops... where 3 pointer copies would be sufficient.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
class Text class Text
{ {
public : public :
// For next section - don't bother yet // For next section - don't bother yet
friend void swap(Text& lhs, Text& rhs); friend void swap(Text& lhs, Text& rhs);
Text(const char* string); Text(const char* string);
// Copy constructor. // Copy constructor.
Text(const Text& t); Text(const Text& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators // Recopy operator; defined here due to an issue of Xeus-cling with operators
Text& operator=(const Text& t) Text& operator=(const Text& t)
{ {
std::cout << "Operator= called" << std::endl; std::cout << "Operator= called" << std::endl;
if (this == &t) if (this == &t)
return *this ; // standard idiom to deal with auto-recopy return *this ; // standard idiom to deal with auto-recopy
delete [] data_; delete [] data_;
size_ = t.size_ ; size_ = t.size_ ;
data_ = new char[t.size_] ; data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_); std::copy(t.data_, t.data_ + size_, data_);
return *this ; return *this ;
} }
~Text(); ~Text();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators. // Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text& t) friend std::ostream & operator<<(std::ostream& stream, const Text& t)
{ {
return stream << t.data_ ; return stream << t.data_ ;
} }
private : private :
unsigned int size_{0}; unsigned int size_{0};
char* data_ = nullptr; char* data_ = nullptr;
} ; } ;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text::Text(const char* string) Text::Text(const char* string)
{ {
std::cout << "Constructor called" << std::endl; std::cout << "Constructor called" << std::endl;
size_ = std::strlen(string) + 1; size_ = std::strlen(string) + 1;
data_ = new char[size_] ; data_ = new char[size_] ;
std::copy(string, string + size_, data_); std::copy(string, string + size_, data_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text::Text(const Text& t) Text::Text(const Text& t)
: size_(t.size_), data_(new char [t.size_]) : size_(t.size_), data_(new char [t.size_])
{ {
std::cout << "Copy constructor called" << std::endl; std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_); std::copy(t.data_, t.data_ + size_, data_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text::~Text() Text::~Text()
{ {
std::cout << "Destructor called" << std::endl; std::cout << "Destructor called" << std::endl;
delete[] data_; delete[] data_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Text t1("world!") ; Text t1("world!") ;
Text t2("Hello") ; Text t2("Hello") ;
// Swap of values: // Swap of values:
Text tmp = t1 ; Text tmp = t1 ;
t1 = t2 ; t1 = t2 ;
t2 = tmp ; t2 = tmp ;
std::cout << t1 << " " << t2 << std::endl; std::cout << t1 << " " << t2 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## A traditional answer: to allow the exchange of internal data ## A traditional answer: to allow the exchange of internal data
By allowing two texts to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time: By allowing two texts to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void swap(Text& lhs, Text& rhs) void swap(Text& lhs, Text& rhs)
{ {
unsigned int tmp_size = lhs.size_; unsigned int tmp_size = lhs.size_;
char* tmp_data = lhs.data_; char* tmp_data = lhs.data_;
lhs.size_ = rhs.size_; lhs.size_ = rhs.size_;
lhs.data_ = rhs.data_; lhs.data_ = rhs.data_;
rhs.size_ = tmp_size; rhs.size_ = tmp_size;
rhs.data_ = tmp_data; rhs.data_ = tmp_data;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Text t1("world!") ; Text t1("world!") ;
Text t2("Hello") ; Text t2("Hello") ;
// Swap of values: // Swap of values:
swap(t1, t2); swap(t1, t2);
std::cout << t1 << " " << t2 << std::endl; std::cout << t1 << " " << t2 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There is even a `std::swap` in the STL that may be overloaded for your own types. There is even a `std::swap` in the STL that may be overloaded for your own types.
Now let's see how C++11 introduces new concepts to solve this (and many other) problems in a more elegant way. Now let's see how C++11 introduces new concepts to solve this (and many other) problems in a more elegant way.
## Reminder on references in C++03 ## Reminder on references in C++03
C++ references allow you to attach a new name to an existing object in the stack or heap. All accesses and modifications made through the reference affect the original object: C++ references allow you to attach a new name to an existing object in the stack or heap. All accesses and modifications made through the reference affect the original object:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
int var = 42; int var = 42;
int& ref = var; // Create a reference to var int& ref = var; // Create a reference to var
ref = 99; ref = 99;
std::cout << "And now var is also 99: " << var << std::endl; std::cout << "And now var is also 99: " << var << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A reference can only be attached to a stable value (left value or **l-value**), which may broadly be summarized as a value which address may be taken (see \cite{Meyers2015} on this topic - its reading is especially interesting concerning this topic that is not always explained properly elsewhere - especially on the Web). A reference can only be attached to a stable value (left value or **l-value**), which may broadly be summarized as a value which address may be taken (see \cite{Meyers2015} on this topic - its reading is especially interesting concerning this topic that is not always explained properly elsewhere - especially on the Web).
By opposition a **r-value** is a temporary value such as a literal expression or a temporary object created by implicit conversion. By opposition a **r-value** is a temporary value such as a literal expression or a temporary object created by implicit conversion.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int& i = 42 ; // Compilation error: 42 is a r-value! int& i = 42 ; // Compilation error: 42 is a r-value!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void print(std::string& lvalue) void print(std::string& lvalue)
{ {
std::cout << "l-value is " << lvalue << std::endl; std::cout << "l-value is " << lvalue << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
print("hello") ; // Compilation error: "hello" is a r-value! print("hello") ; // Compilation error: "hello" is a r-value!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Look carefully at the error message: the issue is not between `const char[6]` and `std::string` (implicit conversion from `char*` to `std::string` exists) but due to the reference; same function with pass-by-copy works seemlessly: Look carefully at the error message: the issue is not between `const char[6]` and `std::string` (implicit conversion from `char*` to `std::string` exists) but due to the reference; same function with pass-by-copy works seemlessly:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
void print_by_copy(std::string value) // no reference here! void print_by_copy(std::string value) // no reference here!
{ {
std::cout << "l- or r- value is " << value << std::endl; std::cout << "l- or r- value is " << value << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
print_by_copy("hello") ; // Ok! print_by_copy("hello") ; // Ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Noteworthy exception: a reference "constant" (language abuse designating a reference to a constant value) can be attached to a temporary value, in particular to facilitate implicit conversions: Noteworthy exception: a "constant" reference (language abuse designating a reference to a constant value) can be attached to a temporary value, in particular to facilitate implicit conversions:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void print_by_const_ref(const std::string& lvalue) void print_by_const_ref(const std::string& lvalue)
{ {
std::cout << "l-value is " << lvalue << std::endl; std::cout << "l-value is " << lvalue << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
print_by_const_ref("hello") ; // Ok! print_by_const_ref("hello") ; // Ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## C++11/14 : temporary references ## C++11/14 : temporary references
To go further, C++11 introduces the concept of **r-value reference**, which can only refer to temporary values, and is declared using an `&&`. To go further, C++11 introduces the concept of **r-value reference**, which can only refer to temporary values, and is declared using an `&&`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int&& i = 42; int&& i = 42;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
int j = 42; int j = 42;
int&& k = j; // Won’t compile: j is a l-value! int&& k = j; // Won’t compile: j is a l-value!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is now possible to overload a function to differentiate the treatment to be performed according to whether it is provided with a stable value or a temporary value. Below, function `f` is provided in three variants: It is now possible to overload a function to differentiate the treatment to be performed according to whether it is provided with a stable value or a temporary value. Below, function `f` is provided in three variants:
```` ````
void f(T&); // I : argument must be a l-value void f(T&); // I : argument must be a l-value
void f(const T&) ; // II : argument may be l-value or r-value but can't be modified void f(const T&) ; // II : argument may be l-value or r-value but can't be modified
void f(T&&); // III : argument must be a r-value void f(T&&); // III : argument must be a r-value
```` ````
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In case of a call of `f` with a temporary value, it is now form III that will be invoked, if it is defined. This is the cornerstone of the notion of **move semantic**. In case of a call of `f` with a temporary value, it is now form III that will be invoked, if it is defined. This is the cornerstone of the notion of **move semantic**.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Function with r-value arguments ## Function with r-value arguments
When we know that a value is temporary, we must be able to use it again, or "loot" its content without harmful consequences; _move_ it instead of _copying_ it. When handling large dynamic data structures, it can save many costly operations. When we know that a value is temporary, we must be able to use it again, or "loot" its content without harmful consequences; _move_ it instead of _copying_ it. When handling large dynamic data structures, it can save many costly operations.
Let's take a function that receives a vector of integers and replicates it to modify it. The old way would be as follows: Let's take a function that receives a vector of integers and replicates it to modify it. The old way would be as follows:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <vector> #include <vector>
void print_double(const std::vector<int>& vec) void print_double(const std::vector<int>& vec)
{ {
std::cout << "print_double for l-value" << std::endl; std::cout << "print_double for l-value" << std::endl;
std::vector<int> copy(vec); std::vector<int> copy(vec);
for (auto& item : copy) for (auto& item : copy)
item *= 2; item *= 2;
for (auto item : copy) for (auto item : copy)
std::cout << item << "\t"; std::cout << item << "\t";
std::cout << std::endl; std::cout << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(primes); print_double(primes);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If the original object is temporary, copying it is not necessary. This can be exploited through this overload of the function: If the original object is temporary, copying it is not necessary. This can be exploited through this overload of the function:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void print_double(std::vector<int>&& vec) void print_double(std::vector<int>&& vec)
{ {
std::cout << "print_double for r-value" << std::endl; std::cout << "print_double for r-value" << std::endl;
for (auto& item : vec) for (auto& item : vec)
item *= 2; item *= 2;
for (auto item : vec) for (auto item : vec)
std::cout << item << "\t"; std::cout << item << "\t";
std::cout << std::endl; std::cout << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
print_double(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 }); print_double(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 });
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `std::move` ## `std::move`
Now, if we get a l-value and know we do not need it anymore in the current scope, we may choose to cast is as a r-value through a **static_cast**: Now, if we get a l-value and know we do not need it anymore in the current scope, we may choose to cast is as a r-value through a **static_cast**:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(static_cast<std::vector<int>&&>(primes)); print_double(static_cast<std::vector<int>&&>(primes));
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And we see overload call is properly the one for r-values. And we see overload call is properly the one for r-values.
The syntax is a bit heavy to type, so a shorter one was introduced as well: **`std::move`**: The syntax is a bit heavy to type, so a shorter one was introduced as well: **`std::move`**:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 };
print_double(std::move(primes)); // strictly equivalent to the static_cast in former cell! print_double(std::move(primes)); // strictly equivalent to the static_cast in former cell!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice that the call to `std::move` does not move `primes` per se. It only makes it a temporary value in the eyes of the compiler, so it is a "possibly" movable object if the context allows it; if for instance the object doesn't define a move constructor (see next section), no move will occur! Please notice that the call to `std::move` does not move `primes` per se. It only makes it a temporary value in the eyes of the compiler, so it is a "possibly" movable object if the context allows it; if for instance the object doesn't define a move constructor (see next section), no move will occur!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Return value optimization (RVO) and copy elision ## Return value optimization (RVO) and copy elision
When you define a function which returns a (possibly large) object, you might be worried unneeded copy is performed: When you define a function which returns a (possibly large) object, you might be worried unneeded copy is performed:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
std::vector<unsigned int> FiveDigitsOfPi() std::vector<unsigned int> FiveDigitsOfPi()
{ {
std::vector<unsigned int> ret { 3, 1, 4, 1, 5 }; std::vector<unsigned int> ret { 3, 1, 4, 1, 5 };
return ret; // copy should be incurred here... Right? (No in fact!) return ret; // copy should be incurred here... Right? (No in fact!)
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and attempt to circumvent it by a `std::move`: and attempt to circumvent it by a `std::move`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
std::vector<unsigned int> FiveDigitsOfPi_WithMove() std::vector<unsigned int> FiveDigitsOfPi_WithMove()
{ {
std::vector<unsigned int> ret { 3, 1, 4, 1, 5 }; std::vector<unsigned int> ret { 3, 1, 4, 1, 5 };
return std::move(ret); // Don't do that! return std::move(ret); // Don't do that!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
or even to avoid entirely returning a large object by using a reference: or even to avoid entirely returning a large object by using a reference:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
void FiveDigitsOfPi(std::vector<unsigned int>& result) void FiveDigitsOfPi(std::vector<unsigned int>& result)
{ {
result = { 3, 1, 4, 1, 5 }; result = { 3, 1, 4, 1, 5 };
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The second version works as you intend, but it way clunkier to use: do you prefer: The second version works as you intend, but it way clunkier to use: do you prefer:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto digits = FiveDigitsOfPi(); auto digits = FiveDigitsOfPi();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
or: or:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::vector<unsigned int> digits; std::vector<unsigned int> digits;
FiveDigitsOfPi(digits); FiveDigitsOfPi(digits);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In fact, you shouldn't worry: all modern compilers provide a __return value optimization__ which guarantees never to copy the potentially large object created. In fact, you shouldn't worry: all modern compilers provide a __return value optimization__ which guarantees never to copy the potentially large object created.
However, it does work only when the object is returned by value, so casting it as a rvalue reference with `std::move(ret)` actually prevents this optimization to kick up! However, it does work only when the object is returned by value, so casting it as a rvalue reference with `std::move(ret)` actually prevents this optimization to kick up!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So to put in a nutshell, you should (almost) never use `std::move` on a return line (you may learn more about it in [this StackOverflow question](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization)). So to put in a nutshell, you should (almost) never use `std::move` on a return line (you may learn more about it in [this StackOverflow question](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization)).
The only exception is detailed in item 25 of \cite{Meyers2015} and is very specific: it is when you want to return a value that was passed by an rvalue argument, e.g.: The only exception is detailed in item 25 of \cite{Meyers2015} and is very specific: it is when you want to return a value that was passed by an rvalue argument, e.g.:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Matrix; // forward declaration - don't bother yet! class Matrix; // forward declaration - don't bother yet!
Matrix Add(Matrix&& lhs, const Matrix& rhs) Matrix Add(Matrix&& lhs, const Matrix& rhs)
{ {
lhs += rhs; lhs += rhs;
return std::move(lhs); // ok in this case! return std::move(lhs); // ok in this case!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This case is very limited (never needed it myself so far) so I invite you to read the item in Scott Meyer's book in you want to learn more (items 23 to 30 are really enlightening about move semantics - very recommended reading!). This case is very limited (never needed it myself so far) so I invite you to read the item in Scott Meyer's book in you want to learn more (items 23 to 30 are really enlightening about move semantics - very recommended reading!).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Move constructors ## Move constructors
In classes, C++ introduced with move semantics two additional elements in the canonical form of the class: In classes, C++ introduced with move semantics two additional elements in the canonical form of the class:
- A **move constructor** - A **move constructor**
- A **move assignment operator** - A **move assignment operator**
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
class Text2 class Text2
{ {
public : public :
friend void swap(Text2& lhs, Text2& rhs); friend void swap(Text2& lhs, Text2& rhs);
Text2(const char* string); Text2(const char* string);
// Copy constructor. // Copy constructor.
Text2(const Text2& t); Text2(const Text2& t);
// Move constructor // Move constructor
Text2(Text2&& t); Text2(Text2&& t);
// Recopy operator; defined here due to an issue of Xeus-cling with operators // Recopy operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(const Text2& t) Text2& operator=(const Text2& t)
{ {
std::cout << "Operator= called" << std::endl; std::cout << "Operator= called" << std::endl;
if (this == &t) if (this == &t)
return *this ; // standard idiom to deal with auto-recopy return *this ; // standard idiom to deal with auto-recopy
delete [] data_; delete [] data_;
size_ = t.size_ ; size_ = t.size_ ;
data_ = new char[t.size_] ; data_ = new char[t.size_] ;
std::copy(t.data_, t.data_ + size_, data_); std::copy(t.data_, t.data_ + size_, data_);
return *this ; return *this ;
} }
// Move assignment operator; defined here due to an issue of Xeus-cling with operators // Move assignment operator; defined here due to an issue of Xeus-cling with operators
Text2& operator=(Text2&& t) Text2& operator=(Text2&& t)
{ {
std::cout << "Operator= called for r-value" << std::endl; std::cout << "Operator= called for r-value" << std::endl;
if (this == &t) if (this == &t)
return *this; return *this;
delete[] data_; delete[] data_;
size_ = t.size_; size_ = t.size_;
data_ = t.data_; data_ = t.data_;
// Don't forget to properly invalidate `t` content: // Don't forget to properly invalidate `t` content:
t.size_ = 0 ; t.size_ = 0 ;
t.data_ = nullptr ; t.data_ = nullptr ;
return *this ; return *this ;
} }
~Text2(); ~Text2();
// Overload of operator<<, defined here due to an issue of Xeus-cling with operators. // Overload of operator<<, defined here due to an issue of Xeus-cling with operators.
friend std::ostream & operator<<(std::ostream& stream, const Text2& t) friend std::ostream & operator<<(std::ostream& stream, const Text2& t)
{ {
return stream << t.data_ ; return stream << t.data_ ;
} }
private : private :
unsigned int size_{0}; unsigned int size_{0};
char* data_ = nullptr; char* data_ = nullptr;
} ; } ;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text2::Text2(const char* string) Text2::Text2(const char* string)
{ {
std::cout << "Constructor called" << std::endl; std::cout << "Constructor called" << std::endl;
size_ = std::strlen(string) + 1; size_ = std::strlen(string) + 1;
data_ = new char[size_] ; data_ = new char[size_] ;
std::copy(string, string + size_, data_); std::copy(string, string + size_, data_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text2::Text2(const Text2& t) Text2::Text2(const Text2& t)
: size_(t.size_), data_(new char [t.size_]) : size_(t.size_), data_(new char [t.size_])
{ {
std::cout << "Copy constructor called" << std::endl; std::cout << "Copy constructor called" << std::endl;
std::copy(t.data_, t.data_ + size_, data_); std::copy(t.data_, t.data_ + size_, data_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text2::Text2(Text2&& t) Text2::Text2(Text2&& t)
: size_(t.size_), data_(t.data_) : size_(t.size_), data_(t.data_)
{ {
std::cout << "Move constructor called" << std::endl; std::cout << "Move constructor called" << std::endl;
t.size_ = 0 ; t.size_ = 0 ;
t.data_ = nullptr ; t.data_ = nullptr ;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Text2::~Text2() Text2::~Text2()
{ {
std::cout << "Destructor called" << std::endl; std::cout << "Destructor called" << std::endl;
delete[] data_; delete[] data_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Text2 t1("world!") ; Text2 t1("world!") ;
Text2 t2("Hello") ; Text2 t2("Hello") ;
// Swap of values: // Swap of values:
Text2 tmp = std::move(t1); Text2 tmp = std::move(t1);
t1 = std::move(t2); t1 = std::move(t2);
t2 = std::move(tmp); t2 = std::move(tmp);
std::cout << t1 << " " << t2 << std::endl; std::cout << t1 << " " << t2 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
With all this move semantics, the operations above are comparable to what we achieved with the `swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values. With all this move semantics, the operations above are comparable to what we achieved with the `swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values.
As already mentioned [there](/notebooks/3-Operators/4-CanonicalForm.ipynb#[Advanced]-The-true-canonical-class), there are specific rules called __Rule of 0__, __Rule of 3__ and __Rule of 5__, which explains which constructor(s), destructor and assigmnent operator you ought to define for your class. As already mentioned [there](../3-Operators/4-CanonicalForm.ipynb#[Advanced]-The-true-canonical-class), there are specific rules called __Rule of 0__, __Rule of 3__ and __Rule of 5__, which explains which constructor(s), destructor and assigmnent operator you ought to define for your class.
## Temporary reference argument within a function ## Temporary reference argument within a function
A crucial point now: if a function receives a temporary reference argument (which can only be attached to a temporary value), within the function this argument is considered as l-value (we can perfectly put it to the left of an = and reassign a new value). If the function does not itself loot the content of the variable, and transmits it to another function (or constructor or operator), it can only reactivate its temporary character using a call to `std::move`. A crucial point now: if a function receives a temporary reference argument (which can only be attached to a temporary value), within the function this argument is considered as l-value (we can perfectly put it to the left of an = and reassign a new value). If the function does not itself loot the content of the variable, and transmits it to another function (or constructor or operator), it can only reactivate its temporary character using a call to `std::move`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
void do_stuff(std::string&& string) void do_stuff(std::string&& string)
{ {
std::cout << "Argument given by r-value is: " << string << std::endl; std::cout << "Argument given by r-value is: " << string << std::endl;
string = "Bye!"; string = "Bye!";
std::cout << "It was nonetheless modified as it is **inside the function** a l-value: " << string << std::endl; std::cout << "It was nonetheless modified as it is **inside the function** a l-value: " << string << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
do_stuff("Hello!"); do_stuff("Hello!");
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Move semantics in the STL ## Move semantics in the STL
All containers in the standard library are now enhanced with move constructors and move assignment operators. All containers in the standard library are now enhanced with move constructors and move assignment operators.
Moreover, the move semantics is not only about improving performance. There are classes (such as `std::unique_ptr` we'll see in [next notebook](/notebooks/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb#unique_ptr)) for which it makes no sense for objects to be copyable, but where it is necessary for them to be movable. In this case, the class has a constructor per move, and no constructor per copy. Moreover, the move semantics is not only about improving performance. There are classes (such as `std::unique_ptr` we'll see in [next notebook](./6-SmartPointers.ipynb#unique_ptr)) for which it makes no sense for objects to be copyable, but where it is necessary for them to be movable. In this case, the class has a constructor per move, and no constructor per copy.
An object that has been "emptied" as a result of a move is no longer supposed to be useful for anything. However if not destroyed it is still recommended, when you implement this type of class and move, to leave the emptied object in a "valid" state; that's why we put in our `Text2` class the `data_` pointer to `nullptr` and the `size_` to 0. The best is of course to ensure its destruction at short notice to avoid any mishap. An object that has been "emptied" as a result of a move is no longer supposed to be useful for anything. However if not destroyed it is still recommended, when you implement this type of class and move, to leave the emptied object in a "valid" state; that's why we put in our `Text2` class the `data_` pointer to `nullptr` and the `size_` to 0. The best is of course to ensure its destruction at short notice to avoid any mishap.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Forwarding reference (or universal reference) ## Forwarding reference (or universal reference)
Just a quick warning (you should really read \cite{Meyers2015} for an extensive discussion on the topic; blog [FluentCpp](https://www.fluentcpp.com/2018/02/06/understanding-lvalues-rvalues-and-their-references) provides some intel about it... and tells you as well to ready Scott Meyer's book to learn more!): seeing `&&` doesn't automatically mean it is a r-value reference. Just a quick warning (you should really read \cite{Meyers2015} for an extensive discussion on the topic; blog [FluentCpp](https://www.fluentcpp.com/2018/02/06/understanding-lvalues-rvalues-and-their-references) provides some intel about it... and tells you as well to ready Scott Meyer's book to learn more!): seeing `&&` doesn't automatically mean it is a r-value reference.
There is a very specific case when: There is a very specific case when:
- The argument is template - The argument is template
- The parameter is **exactly** `T&&` (not `std::vector<T&&>` for instance) - The parameter is **exactly** `T&&` (not `std::vector<T&&>` for instance)
in which the syntax stands for either case (l-value or r-value) in which the syntax stands for either case (l-value or r-value)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T> template<class T>
void PrintUniversalRef(T&& value) void PrintUniversalRef(T&& value)
{ {
std::cout << value << std::endl; std::cout << value << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
PrintUniversalRef("r-value call!"); // will call a specialisation of the template for r-value PrintUniversalRef("r-value call!"); // will call a specialisation of the template for r-value
std::string hello("l-value call!"); // will call a specialisation of the template for l-value std::string hello("l-value call!"); // will call a specialisation of the template for l-value
PrintUniversalRef(hello); PrintUniversalRef(hello);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Unfortunately, C++ 11 committee didn't give a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same. Unfortunately, C++ 11 committee didn't give a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # References
[<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11 [<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237) and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2020_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Useful concepts and STL](/notebooks/5-UsefulConceptsAndSTL/0-main.ipynb) - [Smart pointers](/notebooks/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb) # [Getting started in C++](/) - [Useful concepts and STL](./0-main.ipynb) - [Smart pointers](./6-SmartPointers.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#unique_ptr" data-toc-modified-id="unique_ptr-2"><code>unique_ptr</code></a></span><ul class="toc-item"><li><span><a href="#Usage-to-store-data-in-a-class" data-toc-modified-id="Usage-to-store-data-in-a-class-2.1">Usage to store data in a class</a></span></li><li><span><a href="#Releasing-a-unique_ptr" data-toc-modified-id="Releasing-a-unique_ptr-2.2">Releasing a <code>unique_ptr</code></a></span></li></ul></li><li><span><a href="#shared_ptr" data-toc-modified-id="shared_ptr-3"><code>shared_ptr</code></a></span></li><li><span><a href="#Efficient-storage-with-vectors-of-smart-pointers" data-toc-modified-id="Efficient-storage-with-vectors-of-smart-pointers-4">Efficient storage with vectors of smart pointers</a></span><ul class="toc-item"><li><ul class="toc-item"><li><span><a href="#Using-a-trait-as-syntactic-sugar" data-toc-modified-id="Using-a-trait-as-syntactic-sugar-4.0.1">Using a trait as syntactic sugar</a></span></li></ul></li></ul></li></ul></div> <div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#unique_ptr" data-toc-modified-id="unique_ptr-2"><code>unique_ptr</code></a></span><ul class="toc-item"><li><span><a href="#Usage-to-store-data-in-a-class" data-toc-modified-id="Usage-to-store-data-in-a-class-2.1">Usage to store data in a class</a></span></li><li><span><a href="#Releasing-a-unique_ptr" data-toc-modified-id="Releasing-a-unique_ptr-2.2">Releasing a <code>unique_ptr</code></a></span></li></ul></li><li><span><a href="#shared_ptr" data-toc-modified-id="shared_ptr-3"><code>shared_ptr</code></a></span></li><li><span><a href="#Efficient-storage-with-vectors-of-smart-pointers" data-toc-modified-id="Efficient-storage-with-vectors-of-smart-pointers-4">Efficient storage with vectors of smart pointers</a></span><ul class="toc-item"><li><ul class="toc-item"><li><span><a href="#Using-a-trait-as-syntactic-sugar" data-toc-modified-id="Using-a-trait-as-syntactic-sugar-4.0.1">Using a trait as syntactic sugar</a></span></li></ul></li></ul></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction ## Introduction
In short, **smart pointers** are the application of [RAII](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) to pointers: objects which handle more nicely the acquisition and release of dynamic allocation. In short, **smart pointers** are the application of [RAII](./2-RAII.ipynb) to pointers: objects which handle more nicely the acquisition and release of dynamic allocation.
There are many ways to define the behaviour of a smart pointer (the dedicated chapter in \cite{Alexandrescu2001} is a very interesting read for this, especially as it uses heavily the template [policies](/notebooks/4-Templates/5-MoreAdvanced.ipynb#Policies) to implement his): There are many ways to define the behaviour of a smart pointer (the dedicated chapter in \cite{Alexandrescu2001} is a very interesting read for this, especially as it uses heavily the template [policies](../4-Templates/5-MoreAdvanced.ipynb#Policies) to implement his):
* How the pointer might be copied (or not). * How the pointer might be copied (or not).
* When is the memory freed. * When is the memory freed.
* Whether `if (ptr)` syntax is accepted * Whether `if (ptr)` syntax is accepted
* ... * ...
The STL made the choice of providing two (and a half in fact...) kinds of smart pointers (introduced in C++ 11): The STL made the choice of providing two (and a half in fact...) kinds of smart pointers (introduced in C++ 11):
* **unique pointers** * **unique pointers**
* **shared pointers** (and the **weak** ones that goes along with them). * **shared pointers** (and the **weak** ones that goes along with them).
One should also mention for legacy the first attempt: **auto pointers**, which were removed in C++ 17: you might encounter them in some libraries, but by all means don't use them yourself (look for *sink effect* if you want to know why). One should also mention for legacy the first attempt: **auto pointers**, which were removed in C++ 17: you might encounter them in some libraries, but by all means don't use them yourself (look for *sink effect* on the Web if you want to know why).
By design all smart pointers keep the whole syntax semantic: By design all smart pointers keep the whole syntax semantic:
* `*` to dereference the (now smart) pointer. * `*` to dereference the (now smart) pointer.
* `->` to access an attribute of the underlying object. * `->` to access an attribute of the underlying object.
Smart pointers are clearly a very good way to handle the ownership of a given object. Smart pointers are clearly a very good way to handle the ownership of a given object.
This does not mean they supersede entirely ordinary (often called **raw** or **dumb**) pointers: raw pointers might be a good choice to pass an object as a function parameter (see the discussion for the third question in this [Herb Sutter's post blog](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/)). The raw pointer behind a smart pointer may be accessed through the `get()` method. This does not mean they supersede entirely ordinary (often called **raw** or **dumb**) pointers: raw pointers might be a good choice to pass an object as a function parameter (see the discussion for the third question in this [Herb Sutter's post blog](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/)). The raw pointer behind a smart pointer may be accessed through the `get()` method.
Both smart pointers exposed below may be constructed directly from a raw pointer; in this case they take the responsability of destroying the pointer: Both smart pointers exposed below may be constructed directly from a raw pointer; in this case they take the responsability of destroying the pointer:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
#include <iostream> #include <iostream>
struct Foo struct Foo
{ {
~Foo() ~Foo()
{ {
std::cout << "Destroy foo"<< std::endl; std::cout << "Destroy foo"<< std::endl;
} }
}; };
{ {
Foo* raw = new Foo; Foo* raw = new Foo;
std::unique_ptr<Foo> unique(raw); // Now unique_ptr is responsible for pointer ownership: don't call delete std::unique_ptr<Foo> unique(raw); // Now unique_ptr is responsible for pointer ownership: don't call delete
// on `raw`! Destructor of unique_ptr will call the `Foo` destructor. // on `raw`! Destructor of unique_ptr will call the `Foo` destructor.
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `unique_ptr` ## `unique_ptr`
This should be your first choice for a smart pointer. This should be your first choice for a smart pointer.
The idea behind this smart pointer is that it can't be copied: there is exactly one instance of the smart pointer, and when this instance becomes out of scope the ressources are properly released. The idea behind this smart pointer is that it can't be copied: there is exactly one instance of the smart pointer, and when this instance becomes out of scope the ressources are properly released.
In C++ 11 you had to use the classic `new` syntax to create one, but C++ 14 introduced a specific syntax `make_unique`: In C++ 11 you had to use the classic `new` syntax to create one, but C++ 14 introduced a specific syntax `make_unique`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
{ {
// To be honest `auto` is to my mind better but Xeus-cling yells sometimes; // To be honest `auto` is to my mind better but Xeus-cling yells sometimes;
// a ticket has been opened: https://github.com/QuantStack/xeus-cling/issues/222. // a ticket has been opened: https://github.com/QuantStack/xeus-cling/issues/222.
std::unique_ptr<int> ptr = std::make_unique<int>(5); std::unique_ptr<int> ptr = std::make_unique<int>(5);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The parenthesis takes the constructor arguments. The parenthesis takes the constructor arguments.
The smart pointer can't be copied, but it can be moved: The smart pointer can't be copied, but it can be moved:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
{ {
// `auto` is to my mind better but if Xeus-cling winces replace it by std::unique_ptr<int>. // `auto` is to my mind better but if Xeus-cling winces replace it by std::unique_ptr<int>.
std::unique_ptr<int> ptr = std::make_unique<int>(5); std::unique_ptr<int> ptr = std::make_unique<int>(5);
auto copy = ptr; // COMPILATION ERROR: can't be copied! auto copy = ptr; // COMPILATION ERROR: can't be copied!
} }
``` ```
%% Output
input_line_10:5:10: error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int, std::__1::default_delete<int> >'
auto copy = ptr; // COMPILATION ERROR: can't be copied!
 ^ ~~~
/Users/Shared/Software/miniconda3/envs/formation_cpp_2020/include/c++/v1/memory:2488:3: note: copy constructor is implicitly deleted because 'unique_ptr<int, std::__1::default_delete<int> >' has a user-declared move constructor
unique_ptr(unique_ptr&& __u) _NOEXCEPT
 ^

Interpreter Error:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
{ {
std::unique_ptr<int> ptr = std::make_unique<int>(5); std::unique_ptr<int> ptr = std::make_unique<int>(5);
auto copy = std::move(ptr); // Ok auto copy = std::move(ptr); // Ok - or should be (recent Xeus cling doesn't concur but is wrong...)
} }
``` ```
%% Output
input_line_14:4:26: error: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'
std::unique_ptr<int> copy = std::move(ptr); // Ok
 ^ ~~~~~~~~~~~~~~

Interpreter Error:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As usual with move semantics, beware in this second case: ptr is undefined after the move occurred... As usual with move semantics, beware in this second case: ptr is undefined after the `move` occurred...
### Usage to store data in a class ### Usage to store data in a class
`std::unique_ptr` are a really good choice to store objects in a class, especially ones that do not have a default constructor. The underlying object may be accessed through reference or raw pointer; usually your class may look like: `std::unique_ptr` are a really good choice to store objects in a class, especially ones that do not have a default constructor. The underlying object may be accessed through reference or raw pointer; usually your class may look like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
// Class which will be stored in another one through a `unique_ptr` // Class which will be stored in another one through a `unique_ptr`
class Content class Content
{ {
public: public:
Content(std::string&& text); Content(std::string&& text);
const std::string& GetValue() const; const std::string& GetValue() const;
private: private:
std::string text_ = ""; std::string text_ = "";
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Content::Content(std::string&& text) Content::Content(std::string&& text)
: text_(text) : text_(text)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
const std::string& Content::GetValue() const const std::string& Content::GetValue() const
{ {
return text_; return text_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <memory> #include <memory>
class WithUniquePtr class WithUniquePtr
{ {
public: public:
WithUniquePtr(std::string&& text); WithUniquePtr(std::string&& text);
const Content& GetContent() const; // adding `noexcept` would be even better but Xeus-cling const Content& GetContent() const; // adding `noexcept` would be even better but Xeus-cling
// doesn't like it! // doesn't like it!
private: private:
// A pointer of sort is required here: // A pointer of sort is required here:
// - No default constructor so `Content` can't be stored directly. // - No default constructor so `Content` can't be stored directly.
// - A reference would mean the object is effectively stored elsewhere; we assume // - A reference would mean the object is effectively stored elsewhere; we assume
// we intend here to store the content in the current class. // we intend here to store the content in the current class.
std::unique_ptr<Content> content_ = nullptr; std::unique_ptr<Content> content_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
WithUniquePtr::WithUniquePtr(std::string&& text) WithUniquePtr::WithUniquePtr(std::string&& text)
: content_(std::make_unique<Content>(std::move(text))) : content_(std::make_unique<Content>(std::move(text)))
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <cassert> #include <cassert>
const Content& WithUniquePtr::GetContent() const const Content& WithUniquePtr::GetContent() const
{ {
assert(content_ != nullptr); assert(content_ != nullptr);
return *content_; return *content_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Doing so: Doing so:
* `Content` is stored by a `unique_ptr`, which will manage the destruction in due time of the object (when the `WithUniquePtr` object will be destroyed). * `Content` is stored by a `unique_ptr`, which will manage the destruction in due time of the object (when the `WithUniquePtr` object will be destroyed).
* `Content` object might be manipulated through its reference; end-user don't even need to know ressource was stored through a (smart) pointer: * `Content` object might be manipulated through its reference; end-user don't even need to know ressource was stored through a (smart) pointer:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void PrintContent(const Content& content) void PrintContent(const Content& content)
{ {
std::cout << content.GetValue() << std::endl; std::cout << content.GetValue() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
WithUniquePtr obj("My priceless text here!"); WithUniquePtr obj("My priceless text here!");
decltype(auto) content = obj.GetContent(); decltype(auto) content = obj.GetContent();
PrintContent(content); PrintContent(content);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Releasing a `unique_ptr` ### Releasing a `unique_ptr`
To free manually the content of a `unique_ptr`: To free manually the content of a `unique_ptr`:
* Use `release()` method: * Use `release()` method:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto ptr = std::make_unique<int>(5); auto ptr = std::make_unique<int>(5);
ptr.release(); // Beware: `.` and not `->` as it is a method of the smart pointer class, not of the ptr.release(); // Beware: `.` and not `->` as it is a method of the smart pointer class, not of the
// underlying class! // underlying class!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Or assign `nullptr` to the pointer * Or assign `nullptr` to the pointer
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
auto ptr = std::make_unique<int>(5); auto ptr = std::make_unique<int>(5);
ptr = nullptr; ptr = nullptr;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `shared_ptr` ## `shared_ptr`
The philosophy of `shared_ptr` is different: this kind of smart pointers is fully copyable, and each time a copy is issued an internal counter is incremented (and decremented each time a copy is destroyed). When this counter reaches 0, the underlying object is properly destroyed. The philosophy of `shared_ptr` is different: this kind of smart pointers is fully copyable, and each time a copy is issued an internal counter is incremented (and decremented each time a copy is destroyed). When this counter reaches 0, the underlying object is properly destroyed.
As for `unique_ptr`, there is a specific syntax to build them (properly named `make_shared`...); it was introduced earlier (C++ 11) and is not just cosmetic: the compiler is then able to store the counter more cleverly if you use `make_shared` rather than `new` (so make it so!): As for `unique_ptr`, there is a specific syntax to build them (properly named `make_shared`...); it was introduced earlier (C++ 11) and is not just cosmetic: the compiler is then able to store the counter more cleverly if you use `make_shared` rather than `new` (so make it so!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
std::shared_ptr<double> ptr = std::make_shared<double>(5.); std::shared_ptr<double> ptr = std::make_shared<double>(5.);
auto ptr2 = ptr; auto ptr2 = ptr;
std::cout << "Nptr = " << ptr.use_count() << std::endl; std::cout << "Nptr = " << ptr.use_count() << std::endl;
//< Notice the `.`: we access a method from std::shared_ptr, not from the type encapsulated //< Notice the `.`: we access a method from std::shared_ptr, not from the type encapsulated
// by the pointer! // by the pointer!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`shared_ptr` are clearly useful, but you should always wonder first if you really need them: for most uses a `unique_ptr` eventually seconded by raw pointers extracted by `get()` is enough. `shared_ptr` are clearly useful, but you should always wonder first if you really need them: for most uses a `unique_ptr` eventually seconded by raw pointers extracted by `get()` is enough.
There is also a risk of not releasing properly the memory is there is a circular dependancy between two `shared_ptr`. A variation of this pointer named `weak_ptr` enables to circumvent this issue, but is a bit tedious to put into motion. I have written in [appendix](/notebooks/7-Appendix/WeakPtr.ipynb) to describe how to do so. There is also a risk of not releasing properly the memory is there is a circular dependancy between two `shared_ptr`. A variation of this pointer named `weak_ptr` enables to circumvent this issue, but is a bit tedious to put into motion. I have written in [appendix](../7-Appendix/WeakPtr.ipynb) to describe how to do so.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Efficient storage with vectors of smart pointers ## Efficient storage with vectors of smart pointers
* `std::vector` are cool, but the copy when capacity is exceeded might be very costly for some objects. Moreover, it forces you to provide copy behaviour to your classes intended to be stored in `std::vector`, which is not a good idea if you do not want them to be copied. * `std::vector` are cool, but the copy when capacity is exceeded might be very costly for some objects. Moreover, it forces you to provide copy behaviour to your classes intended to be stored in `std::vector`, which is not a good idea if you do not want them to be copied.
* An idea could be to use pointers: copy is cheap, and there is no need to copy the underlying objects when the capacity is exceeded. Another good point is that a same object might be stored in two different containers, and the modifications given in one of this is immediately "seen" by the other (as the underlying object is the same). * An idea could be to use pointers: copy is cheap, and there is no need to copy the underlying objects when the capacity is exceeded. Another good point is that a same object might be stored in two different containers, and the modifications given in one of this is immediately "seen" by the other (as the underlying object is the same).
However, when this `std::vector` of pointers is destroyed the objects inside aren't properly deleted, provoking memory leaks. However, when this `std::vector` of pointers is destroyed the objects inside aren't properly deleted, provoking memory leaks.
The way to combine advantages without retaining the flaws is to use a vector of smart pointers: The way to combine advantages without retaining the flaws is to use a vector of smart pointers:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <array> #include <array>
class NotCopyable class NotCopyable
{ {
public: public:
NotCopyable(double value); NotCopyable(double value);
~NotCopyable(); ~NotCopyable();
NotCopyable(const NotCopyable& ) = delete; NotCopyable(const NotCopyable& ) = delete;
NotCopyable& operator=(const NotCopyable& ) = delete; NotCopyable& operator=(const NotCopyable& ) = delete;
NotCopyable(NotCopyable&& ) = delete; NotCopyable(NotCopyable&& ) = delete;
NotCopyable& operator=(NotCopyable&& ) = delete; NotCopyable& operator=(NotCopyable&& ) = delete;
private: private:
std::array<double, 1000> data_; std::array<double, 1000> data_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
NotCopyable::NotCopyable(double value) NotCopyable::NotCopyable(double value)
{ {
data_.fill(value); data_.fill(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
NotCopyable::~NotCopyable() NotCopyable::~NotCopyable()
{ {
std::cout << "Call to NotCopyable destructor!" << std::endl; std::cout << "Call to NotCopyable destructor!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <vector> #include <vector>
#include <iostream> #include <iostream>
{ {
std::vector<std::unique_ptr<NotCopyable>> list; std::vector<std::unique_ptr<NotCopyable>> list;
for (double x = 0.; x < 8.; x += 1.1) for (double x = 0.; x < 8.; x += 1.1)
{ {
std::cout << "Capacity = " << list.capacity() << std::endl; std::cout << "Capacity = " << list.capacity() << std::endl;
list.emplace_back(std::make_unique<NotCopyable>(x)); list.emplace_back(std::make_unique<NotCopyable>(x));
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Doing so: Doing so:
- The `NotCopyable` are properly stored in a container. - The `NotCopyable` are properly stored in a container.
- No costly copy occurred: there were just few moves of `unique_ptr` when the capacity was exceeded. - No costly copy occurred: there were just few moves of `unique_ptr` when the capacity was exceeded.
- The memory is properly freed when the `list` becomes out of scope. - The memory is properly freed when the `list` becomes out of scope.
- And as we saw in previous section, the underlying data remains accessible through reference or raw pointer if needed. - And as we saw in previous section, the underlying data remains accessible through reference or raw pointer if needed.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
#### Using a trait as syntactic sugar #### Using a trait as syntactic sugar
I like to create aliases in my classes to provide more readable code: I like to create aliases in my classes to provide more readable code:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <array> #include <array>
#include <vector> #include <vector>
class NotCopyable2 class NotCopyable2
{ {
public: public:
// Trait to alias the vector of smart pointers. // Trait to alias the vector of smart pointers.
using vector_unique_ptr = std::vector<std::unique_ptr<NotCopyable2>>; using vector_unique_ptr = std::vector<std::unique_ptr<NotCopyable2>>;
NotCopyable2(double value); NotCopyable2(double value);
NotCopyable2(const NotCopyable2& ) = delete; NotCopyable2(const NotCopyable2& ) = delete;
NotCopyable2& operator=(const NotCopyable2& ) = delete; NotCopyable2& operator=(const NotCopyable2& ) = delete;
NotCopyable2(NotCopyable2&& ) = delete; NotCopyable2(NotCopyable2&& ) = delete;
NotCopyable2& operator=(NotCopyable2&& ) = delete; NotCopyable2& operator=(NotCopyable2&& ) = delete;
private: private:
std::array<double, 1000> data_; // not copying it too much would be nice! std::array<double, 1000> data_; // not copying it too much would be nice!
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
NotCopyable2::NotCopyable2(double value) NotCopyable2::NotCopyable2(double value)
{ {
data_.fill(value); data_.fill(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
#include<vector> #include<vector>
{ {
// Use the alias // Use the alias
NotCopyable2::vector_unique_ptr list; NotCopyable2::vector_unique_ptr list;
// or not: it amounts to the same! // or not: it amounts to the same!
std::vector<std::unique_ptr<NotCopyable2>> list2; std::vector<std::unique_ptr<NotCopyable2>> list2;
// std::boolalpha is just a stream manipulator to write 'true' or 'false' for a boolean // std::boolalpha is just a stream manipulator to write 'true' or 'false' for a boolean
std::cout << std::boolalpha << std::is_same<NotCopyable2::vector_unique_ptr, std::vector<std::unique_ptr<NotCopyable2>>>() << std::endl; std::cout << std::boolalpha << std::is_same<NotCopyable2::vector_unique_ptr, std::vector<std::unique_ptr<NotCopyable2>>>() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This simplifies the reading, especially if templates are also involved... This simplifies the reading, especially if templates are also involved...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# References # References
[<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu2001</a>] Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001. [<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu2001</a>] Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2020_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_ _This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_ _The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
...@@ -182,14 +182,14 @@ ...@@ -182,14 +182,14 @@
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "C++17",
"language": "C++17", "language": "C++17",
"name": "xeus-cling-cpp17" "name": "xcpp17"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "text/x-c++src",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++",
"version": "-std=c++17" "version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment