diff --git a/notebooks/06_STL.ipynb b/notebooks/06_STL.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a2821c3701dfcaf1d03eb15b40ad22b18ed77070 --- /dev/null +++ b/notebooks/06_STL.ipynb @@ -0,0 +1,1236 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0921615f", + "metadata": {}, + "source": [ + "# Standard Template Library (STL)\n" + ] + }, + { + "cell_type": "markdown", + "id": "eacfe313", + "metadata": {}, + "source": [ + "#### Executive Summary\n", + "* Since its inclusion in 1994 the STL forms an integral part of the **Standard C++ Library**.\n", + "* The STL is composed of three major components:\n", + " 1. Definition of several **container types** for data storage\n", + " 1. Access functions for those containers, especially so called **iterators**\n", + " 1. **Algorithms** for working with the containers.\n", + "* The following table lists the header files to include for different aspects of the STL\n", + " \n", + "| STL containers | Iterators, Functors, Algorithms |\n", + "| :------------------ | :------------------------------ |\n", + "| **```<deque>```** | **```<iterator>```** |\n", + "| **```<list>```** | **```<algorithm>```** |\n", + "| **```<map>```** | **```<functional>```** |\n", + "| **```<queue>```** | |\n", + "| **```<set>```** | |\n", + "| **```<stack>```** | |\n", + "| **```<vector>```** | |\n", + "\n", + "* As its name suggests the STL makes heavy use of *generic programming* based on **templates**." + ] + }, + { + "cell_type": "markdown", + "id": "e3d3641e", + "metadata": {}, + "source": [ + "#### Vector\n", + "* Probably the STL container most often used is **```std::vector```**.\n", + "* It provides an array data-structure that\n", + " 1. can grow and shrink dynamically at runtime\n", + " 2. provides classical index-based access\n", + " 3. allows dynamic bounds checking" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "213d8360", + "metadata": {}, + "outputs": [], + "source": [ + "#include <vector>\n", + "#include <iostream>\n", + "#include <iomanip>\n", + "\n", + "int main() {\n", + "\n", + " const int n = 5;\n", + " \n", + " std::vector< int > vec; // need to tell compiler what we will store inside vec: < int > = template argument\n", + " \n", + " for( int k = 1; k <= n; ++k ) {\n", + " vec.push_back( k*k ); // append new entries at the end\n", + " }\n", + "\n", + " for( int k = 1; k <= n; ++k ) {\n", + " std::cout << \"Square of \" << k << \" is \" << std::setw(2)\n", + " << vec[k-1] << std::endl; // this container allows standard 0-based index access\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bd290a8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Square of 1 is 1\n", + "Square of 2 is 4\n", + "Square of 3 is 9\n", + "Square of 4 is 16\n", + "Square of 5 is 25\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "aa2f2d9d", + "metadata": {}, + "source": [ + "Of course we can also store data of other types in a vector." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0bb0723b", + "metadata": {}, + "outputs": [], + "source": [ + "#include <string>\n", + "\n", + "int main() {\n", + "\n", + " std::vector< double > dVec;\n", + " dVec.push_back( 22.0 );\n", + " dVec.push_back( -1.0 );\n", + " \n", + " std::vector< std::string > sVec;\n", + " sVec.push_back( \"Helena\");\n", + " sVec.push_back( \"Odysseus\" );\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "aca19e34", + "metadata": {}, + "source": [ + "So let's try an example with a **home-made class**. We make the con/destructor/s of our class a little talkative, to better understand what's happening." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e5b2a349", + "metadata": {}, + "outputs": [], + "source": [ + "class Dummy {\n", + "\n", + "public:\n", + "\n", + " Dummy() = delete;\n", + " \n", + " Dummy( const std::string& name ) : name_(name) {\n", + " std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n", + " }\n", + " \n", + " Dummy( const Dummy& other ) {\n", + " name_ = other.getName() + \"_copy\";\n", + " std::cout << \"Object '\" << name_ << \"' was created\" << std::endl;\n", + " }\n", + "\n", + " ~Dummy() {\n", + " std::cout << \"Object '\" << name_ << \"' was destroyed\" << std::endl;\n", + " }\n", + "\n", + " const std::string& getName() const {\n", + " return name_;\n", + " }\n", + "\n", + "private:\n", + " \n", + " std::string name_;\n", + "\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bd331ee4", + "metadata": {}, + "outputs": [], + "source": [ + "int main() {\n", + "\n", + " std::cout << \"-> Creating single objects\" << std::endl;\n", + " Dummy obj1( \"one\" );\n", + " Dummy obj2( \"two\" );\n", + "\n", + " std::cout << \"\\n-> Putting objects in container\" << std::endl;\n", + " std::vector< Dummy > vec;\n", + " vec.push_back( obj1 );\n", + " vec.push_back( obj2 );\n", + "\n", + " std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a04654d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-> Creating single objects\n", + "Object 'one' was created\n", + "Object 'two' was created\n", + "\n", + "-> Putting objects in container\n", + "Object 'one_copy' was created\n", + "Object 'two_copy' was created\n", + "Object 'one_copy_copy' was created\n", + "Object 'one_copy' was destroyed\n", + "\n", + "-> Auto clean-up at program end\n", + "Object 'one_copy_copy' was destroyed\n", + "Object 'two_copy' was destroyed\n", + "Object 'two' was destroyed\n", + "Object 'one' was destroyed\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "e9db8ff6", + "metadata": {}, + "source": [ + "**Observations:**\n", + "1. Apparently ```std::vector``` stores **copies** of the objects we append.\n", + "1. But why is a second copy of **one**, i.e. **one_copy_copy** created?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d4ba36f", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "e36f2bb9", + "metadata": {}, + "source": [ + "This has to do with the fact that an ```std::vector``` can dynamically grow and shrink. Consequently it has two different important properties\n", + "\n", + "* size = number of elements currently stored inside the vector\n", + "* capacity = currently available slots to store entries inside the vector\n", + "\n", + "The capacity of the vector is managed dynamically. We can see this in the following experiment:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "261a8327", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vec: length = 0, capacity = 0\n", + "vec: length = 1, capacity = 1\n", + "vec: length = 2, capacity = 2\n", + "vec: length = 3, capacity = 4\n", + "vec: length = 4, capacity = 4\n", + "vec: length = 5, capacity = 8\n", + "vec: length = 6, capacity = 8\n", + "vec: length = 7, capacity = 8\n", + "vec: length = 8, capacity = 8\n", + "vec: length = 9, capacity = 16\n", + "vec: length = 10, capacity = 16\n", + "vec: length = 11, capacity = 16\n", + "vec: length = 12, capacity = 16\n", + "vec: length = 13, capacity = 16\n", + "vec: length = 14, capacity = 16\n", + "vec: length = 15, capacity = 16\n", + "vec: length = 16, capacity = 16\n", + "vec: length = 17, capacity = 32\n", + "vec: length = 18, capacity = 32\n", + "vec: length = 19, capacity = 32\n", + "vec: length = 20, capacity = 32\n", + "vec: length = 21, capacity = 32\n", + "vec: length = 22, capacity = 32\n", + "vec: length = 23, capacity = 32\n", + "vec: length = 24, capacity = 32\n", + "vec: length = 25, capacity = 32\n", + "vec: length = 26, capacity = 32\n", + "vec: length = 27, capacity = 32\n", + "vec: length = 28, capacity = 32\n", + "vec: length = 29, capacity = 32\n", + "vec: length = 30, capacity = 32\n", + "vec: length = 31, capacity = 32\n", + "vec: length = 32, capacity = 32\n", + "vec: length = 33, capacity = 64\n" + ] + } + ], + "source": [ + "#include <vector>\n", + "#include <iostream>\n", + "#include <iomanip>\n", + "\n", + "int main() {\n", + "\n", + " std::vector< int > vec;\n", + " // vec.reserve( 100 );\n", + " std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n", + " << \", capacity = \" << std::setw(2) << vec.capacity()\n", + " << std::endl;\n", + "\n", + " for( int k = 1; k <= 33; k++ ) {\n", + " vec.push_back( k );\n", + " std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n", + " << \", capacity = \" << std::setw(2) << vec.capacity()\n", + " << std::endl;\n", + " }\n", + "}\n", + "\n", + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "b9d88e30", + "metadata": {}, + "source": [ + "**What happens, when the capacity is exhausted and must be enlarged?**\n", + "\n", + "* ```std::vector<T>``` is basically a wrapper class around a dynamic array ``T* data`` \n", + " *(we can actually access this via the data() member function, e.g. to interface with C or Fortran)* \n", + "* When the capacity is exhausted, but we want to add another entry three steps need to happen\n", + " 1. Dynamical allocation of a new internal data-array with a new larger capacity.\n", + " 1. **Copying** of all exisisting entries from the old data-array to the new one.\n", + " 1. De-allocation of the old data-array.\n", + " \n", + "**Performance issue** \n", + "The memory management, but especially the copying constitutes a significant overhead, especially for large arrays.\n", + "When we already know what the maximal size of our vector will be, we can avoid this, by **reserving** a large enough\n", + "capacity." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "664eec8e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vec: length = 0, capacity = 100\n", + "vec: length = 1, capacity = 100\n", + "vec: length = 2, capacity = 100\n", + "vec: length = 3, capacity = 100\n", + "vec: length = 4, capacity = 100\n", + "vec: length = 5, capacity = 100\n", + "vec: length = 6, capacity = 100\n", + "vec: length = 7, capacity = 100\n", + "vec: length = 8, capacity = 100\n", + "vec: length = 9, capacity = 100\n", + "vec: length = 10, capacity = 100\n", + "vec: length = 11, capacity = 100\n", + "vec: length = 12, capacity = 100\n", + "vec: length = 13, capacity = 100\n", + "vec: length = 14, capacity = 100\n", + "vec: length = 15, capacity = 100\n", + "vec: length = 16, capacity = 100\n", + "vec: length = 17, capacity = 100\n", + "vec: length = 18, capacity = 100\n", + "vec: length = 19, capacity = 100\n", + "vec: length = 20, capacity = 100\n", + "vec: length = 21, capacity = 100\n", + "vec: length = 22, capacity = 100\n", + "vec: length = 23, capacity = 100\n", + "vec: length = 24, capacity = 100\n", + "vec: length = 25, capacity = 100\n", + "vec: length = 26, capacity = 100\n", + "vec: length = 27, capacity = 100\n", + "vec: length = 28, capacity = 100\n", + "vec: length = 29, capacity = 100\n", + "vec: length = 30, capacity = 100\n", + "vec: length = 31, capacity = 100\n", + "vec: length = 32, capacity = 100\n", + "vec: length = 33, capacity = 100\n" + ] + } + ], + "source": [ + "#include <vector>\n", + "#include <iostream>\n", + "#include <iomanip>\n", + "\n", + "int main() {\n", + "\n", + " std::vector< int > vec;\n", + "\n", + " // make the vector allocate a data-array of sufficient length\n", + " vec.reserve( 100 );\n", + " \n", + " std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n", + " << \", capacity = \" << std::setw(2) << vec.capacity()\n", + " << std::endl;\n", + "\n", + " for( int k = 1; k <= 33; k++ ) {\n", + " vec.push_back( k );\n", + " std::cout << \"vec: length = \" << std::setw(2) << vec.size()\n", + " << \", capacity = \" << std::setw(2) << vec.capacity()\n", + " << std::endl;\n", + " }\n", + "}\n", + "\n", + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "2352d31e", + "metadata": {}, + "source": [ + "This will also work for our original example with the class Dummy:" + ] + }, + { + "cell_type": "markdown", + "id": "8a69af05", + "metadata": {}, + "source": [ + "int main() {\n", + "\n", + " std::cout << \"-> Creating single objects\" << std::endl;\n", + " Dummy obj1( \"one\" );\n", + " Dummy obj2( \"two\" );\n", + "\n", + " std::cout << \"\\n-> Putting objects in container\" << std::endl;\n", + " std::vector< Dummy > vec;\n", + " vec.reserve( 2 );\n", + " vec.push_back( obj1 );\n", + " vec.push_back( obj2 );\n", + "\n", + " std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n", + "}\n", + "\n", + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "41bf0d83", + "metadata": {}, + "source": [ + "**But:** Wouldn't it be nicer/easier to have the reservation be part of the instantiation? Let's check:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "351ace20", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "In file included from input_line_5:1:\n", + "In file included from /home/mohr/local/miniconda3/envs/cling/include/xeus/xinterpreter.hpp:15:\n", + "In file included from /home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/vector:65:\n", + "\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_construct.h:75:38: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1mcall to deleted constructor of '__cling_N56::Dummy'\u001b[0m\n", + " { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_uninitialized.h:545:8: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of function template specialization\n", + " 'std::_Construct<__cling_N56::Dummy>' requested here\u001b[0m\n", + " std::_Construct(std::__addressof(*__cur));\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_uninitialized.h:601:2: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of function template specialization\n", + " 'std::__uninitialized_default_n_1<false>::__uninit_default_n<__cling_N56::Dummy\n", + " *, unsigned long>' requested here\u001b[0m\n", + " __uninit_default_n(__first, __n);\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_uninitialized.h:663:19: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of function template specialization\n", + " 'std::__uninitialized_default_n<__cling_N56::Dummy *, unsigned long>'\n", + " requested here\u001b[0m\n", + " { return std::__uninitialized_default_n(__first, __n); }\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_vector.h:1603:9: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of function template specialization\n", + " 'std::__uninitialized_default_n_a<__cling_N56::Dummy *, unsigned long,\n", + " __cling_N56::Dummy>' requested here\u001b[0m\n", + " std::__uninitialized_default_n_a(this->_M_impl._M_start, __n,\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1m/home/mohr/local/miniconda3/envs/cling/bin/../lib/gcc/x86_64-conda-linux-gnu/9.3.0/../../../../x86_64-conda-linux-gnu/include/c++/9.3.0/bits/stl_vector.h:509:9: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of member function 'std::vector<__cling_N56::Dummy,\n", + " std::allocator<__cling_N56::Dummy> >::_M_default_initialize' requested\n", + " here\u001b[0m\n", + " { _M_default_initialize(__n); }\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1minput_line_24:2:23: \u001b[0m\u001b[0;1;30mnote: \u001b[0min instantiation of member function 'std::vector<__cling_N56::Dummy,\n", + " std::allocator<__cling_N56::Dummy> >::vector' requested here\u001b[0m\n", + " std::vector< Dummy > vec( 2 );\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m\u001b[1minput_line_13:3:5: \u001b[0m\u001b[0;1;30mnote: \u001b[0m'Dummy' has been explicitly marked deleted here\u001b[0m\n", + " Dummy() = delete;\n", + "\u001b[0;1;32m ^\n", + "\u001b[0m" + ] + }, + { + "ename": "Interpreter Error", + "evalue": "", + "output_type": "error", + "traceback": [ + "Interpreter Error: " + ] + } + ], + "source": [ + "std::vector< Dummy > vec( 2 );" + ] + }, + { + "cell_type": "markdown", + "id": "ec2de34b", + "metadata": {}, + "source": [ + "Problem is that this version of the ```std::vector<T>``` constructor will try to fill/initialise the vector with objects of type ```T``` using their default constructor." + ] + }, + { + "cell_type": "markdown", + "id": "d1cdc134", + "metadata": {}, + "source": [ + "Let us take a look at other ways to construct an std::vector:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b3dfe338", + "metadata": {}, + "outputs": [], + "source": [ + "int main() {\n", + "\n", + " std::cout << \"-> Creating single objects\" << std::endl;\n", + " Dummy obj1( \"one\" );\n", + " Dummy obj2( \"two\" );\n", + "\n", + " std::cout << \"\\n-> Putting objects in container\" << std::endl;\n", + " std::vector< Dummy > vec;\n", + " vec.reserve( 10 );\n", + "\n", + " // Will fail, as we have no default constructor\n", + " // std::vector< Dummy > vec( 2 );\n", + "\n", + " vec.push_back( obj1 );\n", + " vec.push_back( obj2 );\n", + "\n", + " // std::vector< Dummy > vec2( vec ); -> construction by copying\n", + " // std::vector< Dummy > vec2 = vec; -> construction by copying (but move semantics?)\n", + " // std::vector< Dummy > vec2( 5, obj1 ); -> vector with 5 copies of object one\n", + " // std::vector< Dummy > vec2( {obj1, obj2 } ); -> using initialiser list (but is a temporary generated here?)\n", + " // std::vector< Dummy > vec2{obj1, obj2 }; -> clearer version with initialiser list\n", + "\n", + " vec.emplace_back( \"three\" ); // <- what will this do?\n", + "\n", + " std::cout << \"\\n-> Auto clean-up at program end\" << std::endl;\n", + "\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ad928c13", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-> Creating single objects\n", + "Object 'one' was created\n", + "Object 'two' was created\n", + "\n", + "-> Putting objects in container\n", + "Object 'one_copy' was created\n", + "Object 'two_copy' was created\n", + "Object 'three' was created\n", + "\n", + "-> Auto clean-up at program end\n", + "Object 'one_copy' was destroyed\n", + "Object 'two_copy' was destroyed\n", + "Object 'three' was destroyed\n", + "Object 'two' was destroyed\n", + "Object 'one' was destroyed\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "eebc1269", + "metadata": {}, + "source": [ + "* ```emplace()``` allows to **generate** a new entry at a specified place inside the vector, passing the given arguments to the entry's constructor.\n", + "* ```emplace_back()``` does the same, but **appends at the end**." + ] + }, + { + "cell_type": "markdown", + "id": "dc64b2c9", + "metadata": {}, + "source": [ + "***\n", + "We have already seen that we have read/write access to entries of our vector with the **```[ ]```** operator. Another alternative is the **```at()```** method.\n", + "\n", + "The difference is that **```at()```** will throw an exception, when we commit an out-of-bounds access error." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "7e537a6b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting exception.cpp\n" + ] + } + ], + "source": [ + "%%file exception.cpp\n", + "\n", + "#include <iostream>\n", + "#include <vector>\n", + "\n", + "int main() {\n", + "\n", + " std::vector< double > vec( 10, 2.0 );\n", + "\n", + "#ifdef OUT_OF_BOUNDS\n", + " for( int k = 0; k <= 10; ++k ) {\n", + " std::cout << \"vec[ \" << k << \" ] = \" << vec[k] << std::endl;\n", + " }\n", + " std::cout << \"Too bad we reached this line :-(\" << std::endl;\n", + "#else\n", + " for( int k = 0; k <= 10; ++k ) {\n", + " std::cout << \"vec[ \" << k << \" ] = \" << vec.at( k )\n", + " << std::endl;\n", + " }\n", + "#endif\n", + "\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "0513b187", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -DOUT_OF_BOUNDS exception.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "af593caa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vec[ 0 ] = 2\n", + "vec[ 1 ] = 2\n", + "vec[ 2 ] = 2\n", + "vec[ 3 ] = 2\n", + "vec[ 4 ] = 2\n", + "vec[ 5 ] = 2\n", + "vec[ 6 ] = 2\n", + "vec[ 7 ] = 2\n", + "vec[ 8 ] = 2\n", + "vec[ 9 ] = 2\n", + "vec[ 10 ] = 0\n", + "Too bad we reached this line :-(\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "131dd4f5", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall exception.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "e6ce23bd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vec[ 0 ] = 2\n", + "vec[ 1 ] = 2\n", + "vec[ 2 ] = 2\n", + "vec[ 3 ] = 2\n", + "vec[ 4 ] = 2\n", + "vec[ 5 ] = 2\n", + "vec[ 6 ] = 2\n", + "vec[ 7 ] = 2\n", + "vec[ 8 ] = 2\n", + "vec[ 9 ] = 2\n", + "terminate called after throwing an instance of 'std::out_of_range'\n", + " what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 10)\n", + "Aborted (core dumped)\n" + ] + } + ], + "source": [ + "!./a.out" + ] + }, + { + "cell_type": "markdown", + "id": "8e0987ec", + "metadata": {}, + "source": [ + "### Iterators and range-based access" + ] + }, + { + "cell_type": "markdown", + "id": "80b33721", + "metadata": {}, + "source": [ + "Our **std::vector** belongs to the class of sequence containers, i.e. the ordering of entries $e_k$ is important\n", + "\n", + "$$ \\Big( e_1, e_2, \\ldots, e_n \\Big)$$\n", + "\n", + "and the same value $v$ can show up at multiple positions ($e_i = v = e_j$ with $i\\neq j$).\n", + "\n", + "Another type of sequence containers is the **linked list**. The **std::list** implements a doubly-linked list, i.e. each entry/element is composed of three parts\n", + "\n", + "$$ \\text{entry} = \\Big( \\text{payload}, \\text{prev}, \\text{next} \\Big)$$\n", + "\n", + "where\n", + " * payload = value of the entry\n", + " * prev = information on where to find the predecessor of this entry\n", + " * next = information on where to find the successor of this entry\n", + " \n", + "Contrary to a vector, a list does not offer index-based access to its elements. Instead we can do list-traversal.\n", + "\n", + "Now consider the following piece of code:" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "eee3b34a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "box(0) = 1\n", + "box(1) = 3\n", + "box(2) = 5\n", + "box(3) = 7\n", + "box(4) = 9\n" + ] + } + ], + "source": [ + "#include <iostream>\n", + "#include <vector>\n", + "\n", + "int main() {\n", + " using Container = std::vector< int >;\n", + " Container box;\n", + "\n", + " const int n = 5;\n", + " \n", + " // fill the box with odd numbers\n", + " for( int k = 0; k < n; k++ ) {\n", + " box.push_back( 2*k+1 );\n", + " }\n", + " \n", + " // print its contents\n", + " for( int k = 0; k < n; k++ ) {\n", + " std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n", + " }\n", + "}\n", + "\n", + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "33c83653", + "metadata": {}, + "source": [ + "For what we are doing in the program the type of underlying container does not really matter. \n", + "How about replacing vector with list for a change?" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "d2806a4f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[1minput_line_59:13:50: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1mtype 'Container' (aka 'list<int>') does not provide a subscript operator\u001b[0m\n", + " std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n", + "\u001b[0;1;32m ~~~^~\n", + "\u001b[0m" + ] + }, + { + "ename": "Interpreter Error", + "evalue": "", + "output_type": "error", + "traceback": [ + "Interpreter Error: " + ] + } + ], + "source": [ + "#include <iostream>\n", + "#include <list> // okay, need to adapt the include\n", + "\n", + "int main() {\n", + " using Container = std::list< int >; // need to alter this statement\n", + " Container box;\n", + "\n", + " const int n = 5;\n", + " \n", + " // fill the box with odd numbers\n", + " for( int k = 0; k < n; k++ ) {\n", + " box.push_back( 2*k+1 );\n", + " }\n", + " \n", + " // print its contents\n", + " for( int k = 0; k < n; k++ ) {\n", + " std::cout << \"box(\" << k << \") = \" << box[k] << std::endl;\n", + " }\n", + "}\n", + "\n", + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "40d19e9a", + "metadata": {}, + "source": [ + "***\n", + "Now that is, where **iterators** come into play. They abstract away the details of the underlying container." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4a6ba619", + "metadata": {}, + "outputs": [], + "source": [ + "// %%file fubar.cpp\n", + "\n", + "#include <iostream>\n", + "#include <vector>\n", + "\n", + "int main() {\n", + " using Container = std::vector< int >;\n", + " Container box;\n", + "\n", + " const int n = 5;\n", + " \n", + " // fill the box with odd numbers\n", + " for( int k = 0; k < n; k++ ) {\n", + " box.push_back( 2*k+1 );\n", + " }\n", + " \n", + " // use an iterator-based loop\n", + " int k = 0;\n", + " for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n", + " std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n", + " k++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "907c51a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "box(0) = 1\n", + "box(1) = 3\n", + "box(2) = 5\n", + "box(3) = 7\n", + "box(4) = 9\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "5b9766a5", + "metadata": {}, + "source": [ + "**Remarks:**\n", + "* The iterator is no pointer, but 'feels' like once, as it overloads the dereferencing operator ```*```.\n", + "* ```box.begin()``` points to the first entry in the container.\n", + "* ```box.end()``` points **after** the last entry in the container. Thus, dereferencing it would be illegal.\n", + "* Do not test for ```it < box.end()```, but for (in)equality only." + ] + }, + { + "cell_type": "markdown", + "id": "ba14f11e", + "metadata": {}, + "source": [ + "Since our loop is using an iterator now, we can with two small modification switch from a **std::vector** to a **std::list**." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9f8daf26", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "#include <list>\n", + "\n", + "int main() {\n", + " using Container = std::list< int >;\n", + " Container box;\n", + "\n", + " const int n = 5;\n", + " \n", + " // fill the box with odd numbers\n", + " for( int k = 0; k < n; k++ ) {\n", + " box.push_back( 2*k+1 );\n", + " }\n", + " \n", + " // use an iterator-based loop\n", + " int k = 0;\n", + " for( Container::iterator it = box.begin(); it != box.end(); it++ ) {\n", + " std::cout << \"box(\" << k << \") = \" << *it << std::endl;\n", + " k++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "53211059", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "box(0) = 1\n", + "box(1) = 3\n", + "box(2) = 5\n", + "box(3) = 7\n", + "box(4) = 9\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "44f9f4e2", + "metadata": {}, + "source": [ + "#### 'Reverse mode' \n", + "Up above we have used a forward iterator. However, we can also ask for a reverse iterator to go through the container from end to start." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "158950d1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n", + "2\n", + "1\n" + ] + } + ], + "source": [ + "#include <iostream>\n", + "\n", + "std::vector< short > vec;\n", + "\n", + "vec.push_back( 1 );\n", + "vec.push_back( 2 );\n", + "vec.push_back( 3 );\n", + "\n", + "for( std::vector< short >::reverse_iterator it = vec.rbegin(); it != vec.rend(); it++ ) {\n", + " std::cout << *it << std::endl;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "96bed53d", + "metadata": {}, + "source": [ + "#### 'Const mode'\n", + "When we do not intend to change the element in the loop body, we can also employ a ```const_iterator```." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "44a9bf28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n", + "5\n", + "6\n" + ] + } + ], + "source": [ + "std::list< double > lc;\n", + "using cIter = std::list< double >::const_iterator;\n", + "lc.push_back( 4.0 );\n", + "lc.push_back( 5.0 );\n", + "lc.push_back( 6.0 );\n", + "for( cIter it = lc.begin(); it != lc.end(); it++ ) {\n", + " std::cout << *it << std::endl;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c731df99", + "metadata": {}, + "source": [ + "### Range-based access\n", + "C++ nowadays also supports and even simpler way to encode a loop over a container:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2497a826", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "#include <list>\n", + "\n", + "int main() {\n", + " using Container = std::list< int >;\n", + " Container box;\n", + "\n", + " const int n = 5;\n", + " \n", + " // fill the box with odd numbers\n", + " for( int k = 0; k < n; k++ ) {\n", + " box.push_back( 2*k+1 );\n", + " }\n", + " \n", + " // change that to even, using a range-based loop\n", + " for( int& entry: box ) {\n", + " entry -= 1;\n", + " }\n", + " \n", + " // print using a range-based loop\n", + " int k = 0;\n", + " for( int value: box ) {\n", + " std::cout << \"box(\" << k << \") = \" << value << std::endl;\n", + " k++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a8918a0e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "box(0) = 0\n", + "box(1) = 2\n", + "box(2) = 4\n", + "box(3) = 6\n", + "box(4) = 8\n" + ] + } + ], + "source": [ + "main();" + ] + }, + { + "cell_type": "markdown", + "id": "f4d8fd5e", + "metadata": {}, + "source": [ + "Note that the last loop version involved a copy operation!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d1c64b51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-> Put objects into container\n", + "Object 'one' was created\n", + "Object 'two' was created\n", + "\n", + "-> Run a loop\n", + "Object 'one_copy' was created\n", + "* one_copy\n", + "Object 'one_copy' was destroyed\n", + "Object 'two_copy' was created\n", + "* two_copy\n", + "Object 'two_copy' was destroyed\n" + ] + } + ], + "source": [ + "std::cout << \"-> Put objects into container\" << std::endl;\n", + "std::vector< Dummy > vec;\n", + "vec.reserve(2);\n", + "vec.emplace_back( \"one\" );\n", + "vec.emplace_back( \"two\" );\n", + "\n", + "std::cout << \"\\n-> Run a loop\" << std::endl;\n", + "for( Dummy x: vec ) {\n", + " std::cout << \"* \" << x.getName() << std::endl;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28b2e3fb", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "C++14", + "language": "C++14", + "name": "xcpp14" + }, + "language_info": { + "codemirror_mode": "text/x-c++src", + "file_extension": ".cpp", + "mimetype": "text/x-c++src", + "name": "c++", + "version": "14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}