Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 255b4cb2 authored by ROUVREAU Vincent's avatar ROUVREAU Vincent
Browse files

Merge branch 'container_element_access_like_python' into 'master'

Python like syntax for container element access

Closes #104

See merge request !83
parents 49e1287c 8543e9de
No related branches found
No related tags found
1 merge request!83Python like syntax for container element access
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Useful concepts and STL](./0-main.ipynb) - [Containers](./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:
```c++ ```c++
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;
} }
``` ```
%% 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;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
* Since C++ 11, constructor with the initial content (prior to C++ 11 you had to use an empty constructor and then add all elements one by one with something like `push_back` (see below) or use a third party library such as Boost::Assign). * Since C++ 11, constructor with the initial content (prior to C++ 11 you had to use an empty constructor and then add all elements one by one with something like `push_back` (see below) or use a third party library such as Boost::Assign).
%% 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;
} }
``` ```
%% 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;
} }
``` ```
%% 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 independently: 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 independently: 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;
} }
``` ```
%% 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 way 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;
} }
``` ```
%% 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; // Remember: indexing starts at 0 in C and C++ std::cout << "foo[1] = " << foo[1] << std::endl; // Remember: indexing starts at 0 in C and C++
} }
``` ```
%% 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
} }
``` ```
%% 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
} }
``` ```
%% 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 area 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 area 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);
} }
} }
``` ```
%% 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 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: `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);
} }
} }
``` ```
%% 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>
// Helper function to avoid typing endlessly the same lines... // Helper function to avoid typing endlessly the same lines...
template<class VectorT> template<class VectorT>
void PrintVector(const VectorT& vector) void PrintVector(const VectorT& vector)
{ {
auto size = vector.size(); auto size = vector.size();
std::cout << "Size = " << size << " Capacity = " << vector.capacity() << " Content = [ "; std::cout << "Size = " << size << " Capacity = " << vector.capacity() << " Content = [ ";
for (auto item : vector) for (auto item : vector)
std::cout << item << ' '; std::cout << item << ' ';
std::cout << ']' << std::endl; std::cout << ']' << 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::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(12); // If not specified, a default value is used - here 0 for a POD foo.resize(12); // If not specified, a default value is used - here 0 for a POD
// The default value is the same as the one that would be used when constructing // The default value is the same as the one that would be used when constructing
// an element with empty braces - here `std::size_t myvariable {}`; // an element with empty braces - here `std::size_t myvariable {}`;
PrintVector(foo); PrintVector(foo);
foo.resize(3, 15); foo.resize(3, 15);
PrintVector(foo); PrintVector(foo);
} }
``` ```
%% 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);
} }
``` ```
%% 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);
PrintVector(five_pi_digits); // not what we intended! PrintVector(five_pi_digits); // not what we intended!
} }
``` ```
%% 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()`
%% 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] << " ";
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
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...) 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 (possibly) more efficient access: Iterators provides this (possibly) 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 << " "; std::cout << *it << " ";
} }
``` ```
%% 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](../2-ObjectProgramming/6-inheritance.ipynb#IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-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](../2-ObjectProgramming/6-inheritance.ipynb#IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-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 (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...) * 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);
} }
``` ```
%% 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 iterate over it. So, the bottom line is you should really separate actions that modify the structure of a container and iterate 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 << " ";
} }
``` ```
%% 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:
## Access a container element - Python like syntax
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
std::vector<int> v {1, // can be accessed with begin()[0] or end()[-4]
2, // can be accessed with begin()[1] or end()[-3]
3, // can be accessed with begin()[2] or end()[-2]
4 // can be accessed with begin()[3] or end()[-1]
};
std::cout << v.end()[-2] << " - " << v.begin()[1] << std::endl;
// Displays '3 - 2'
// But also some weird pointer value (something like @0x7ffff91e0de0) with Xeus-cling. Just forget about it, it looks like a Xeus-cling bug.
```
%% 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 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::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 when storing booleans that went wrong and should therefore be avoided... * `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 when storing booleans 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, as compiler may provide even more optimizations than for `std::vector` (and your end user can't by mistake modify its size). * `std::array`: You should use this one if the number of elements is known at compile time and doesn't change at all, as compiler may provide even more optimizations than for `std::vector` (and your end user can't by mistake modify its size).
* `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` and `std::array` that ensures contiguous 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` and `std::array` that ensures contiguous 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:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment