diff --git a/notebooks/06_STL.ipynb b/notebooks/06_STL.ipynb index 0aed4c7d4d047249e0c7760c6e3db7108c1e32e7..644d31f6cc3d289880d75d72d619316bf8285d15 100644 --- a/notebooks/06_STL.ipynb +++ b/notebooks/06_STL.ipynb @@ -1 +1,1249 @@ -{"metadata":{"orig_nbformat":4,"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_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Standard Template Library (STL)\n","metadata":{},"id":"0921615f"},{"cell_type":"markdown","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**.","metadata":{},"id":"eacfe313"},{"cell_type":"markdown","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","metadata":{},"id":"e3d3641e"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip> // required for setw() below\n\nint main() {\n\n const int n = 5;\n \n std::vector< int > vec; // need to tell compiler what we will\n // 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 subscript access\n }\n}","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"213d8360"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"Square of 1 is 1\nSquare of 2 is 4\nSquare of 3 is 9\nSquare of 4 is 16\nSquare of 5 is 25\n","output_type":"stream"}],"id":"bd290a8a"},{"cell_type":"markdown","source":"Of course we can also store data of other types in a vector.","metadata":{},"id":"aa2f2d9d"},{"cell_type":"code","source":"#include <string>\n\nint 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}","metadata":{"trusted":true},"execution_count":3,"outputs":[],"id":"0bb0723b"},{"cell_type":"markdown","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.","metadata":{},"id":"aca19e34"},{"cell_type":"code","source":"class Dummy {\n\npublic:\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\nprivate:\n \n std::string name_;\n\n};","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"e5b2a349"},{"cell_type":"code","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}","metadata":{"trusted":true},"execution_count":5,"outputs":[],"id":"bd331ee4"},{"cell_type":"code","source":"main();","metadata":{"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\nObject 'one_copy_copy' was created\nObject 'one_copy' was destroyed\n\n-> Auto clean-up at program end\nObject 'one_copy_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a04654d2"},{"cell_type":"markdown","source":"**Observations:**\n1. Apparently ```std::vector``` stores **copies** of the objects we append.\n1. But why is a second copy of **one**, i.e. **one_copy_copy** created?","metadata":{},"id":"e9db8ff6"},{"cell_type":"code","source":" // Suggestions anyone?\n\n\n","metadata":{},"execution_count":null,"outputs":[],"id":"5d4ba36f"},{"cell_type":"markdown","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\nThe capacity of the vector is managed dynamically. We can see this in the following experiment:","metadata":{},"id":"e36f2bb9"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint main() {\n\n std::vector< int > vec;\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\nmain();","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 0\nvec: length = 1, capacity = 1\nvec: length = 2, capacity = 2\nvec: length = 3, capacity = 4\nvec: length = 4, capacity = 4\nvec: length = 5, capacity = 8\nvec: length = 6, capacity = 8\nvec: length = 7, capacity = 8\nvec: length = 8, capacity = 8\nvec: length = 9, capacity = 16\nvec: length = 10, capacity = 16\nvec: length = 11, capacity = 16\nvec: length = 12, capacity = 16\nvec: length = 13, capacity = 16\nvec: length = 14, capacity = 16\nvec: length = 15, capacity = 16\nvec: length = 16, capacity = 16\nvec: length = 17, capacity = 32\nvec: length = 18, capacity = 32\nvec: length = 19, capacity = 32\nvec: length = 20, capacity = 32\nvec: length = 21, capacity = 32\nvec: length = 22, capacity = 32\nvec: length = 23, capacity = 32\nvec: length = 24, capacity = 32\nvec: length = 25, capacity = 32\nvec: length = 26, capacity = 32\nvec: length = 27, capacity = 32\nvec: length = 28, capacity = 32\nvec: length = 29, capacity = 32\nvec: length = 30, capacity = 32\nvec: length = 31, capacity = 32\nvec: length = 32, capacity = 32\nvec: length = 33, capacity = 64\n","output_type":"stream"}],"id":"261a8327"},{"cell_type":"markdown","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 *[That's what gave us \"Object 'one_copy_copy' was created\" above]*\n 1. Destruction of objects in old data-array and its de-allocation. \n *[That's what gave us \"Object 'one_copy' was destroyed\" above]*\n* Afterwards the new entry can be appended.\n\n**Performance issue** \nThe memory management, but especially the copying constitutes a significant overhead, especially for large arrays.\nWhen we already know what the maximal size of our vector will be, we can avoid this, by **reserving** a large enough\ncapacity.","metadata":{},"id":"b9d88e30"},{"cell_type":"code","source":"#include <vector>\n#include <iostream>\n#include <iomanip>\n\nint 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\nmain();","metadata":{"trusted":true},"execution_count":8,"outputs":[{"name":"stdout","text":"vec: length = 0, capacity = 100\nvec: length = 1, capacity = 100\nvec: length = 2, capacity = 100\nvec: length = 3, capacity = 100\nvec: length = 4, capacity = 100\nvec: length = 5, capacity = 100\nvec: length = 6, capacity = 100\nvec: length = 7, capacity = 100\nvec: length = 8, capacity = 100\nvec: length = 9, capacity = 100\nvec: length = 10, capacity = 100\nvec: length = 11, capacity = 100\nvec: length = 12, capacity = 100\nvec: length = 13, capacity = 100\nvec: length = 14, capacity = 100\nvec: length = 15, capacity = 100\nvec: length = 16, capacity = 100\nvec: length = 17, capacity = 100\nvec: length = 18, capacity = 100\nvec: length = 19, capacity = 100\nvec: length = 20, capacity = 100\nvec: length = 21, capacity = 100\nvec: length = 22, capacity = 100\nvec: length = 23, capacity = 100\nvec: length = 24, capacity = 100\nvec: length = 25, capacity = 100\nvec: length = 26, capacity = 100\nvec: length = 27, capacity = 100\nvec: length = 28, capacity = 100\nvec: length = 29, capacity = 100\nvec: length = 30, capacity = 100\nvec: length = 31, capacity = 100\nvec: length = 32, capacity = 100\nvec: length = 33, capacity = 100\n","output_type":"stream"}],"id":"664eec8e"},{"cell_type":"markdown","source":"***\nThis will also work for our original example with the class Dummy:","metadata":{},"id":"2352d31e"},{"cell_type":"code","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\nmain();","metadata":{"trusted":true},"execution_count":9,"outputs":[{"name":"stdout","text":"-> Creating single objects\nObject 'one' was created\nObject 'two' was created\n\n-> Putting objects in container\nObject 'one_copy' was created\nObject 'two_copy' was created\n\n-> Auto clean-up at program end\nObject 'one_copy' was destroyed\nObject 'two_copy' was destroyed\nObject 'two' was destroyed\nObject 'one' was destroyed\n","output_type":"stream"}],"id":"a5dffa46-65b8-477b-bbd8-a0ad0801d1fb"},{"cell_type":"markdown","source":"**But:** Wouldn't it be nicer/easier to have the reservation be part of the instantiation? Let's check:","metadata":{},"id":"41bf0d83"},{"cell_type":"code","source":"std::vector< Dummy > vec( 2 );","metadata":{},"execution_count":null,"outputs":[],"id":"351ace20"},{"cell_type":"markdown","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.","metadata":{},"id":"ec2de34b"},{"cell_type":"markdown","source":"Let us take a look at other ways to construct an std::vector:","metadata":{},"id":"d1cdc134"},{"cell_type":"code","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}","metadata":{},"execution_count":null,"outputs":[],"id":"b3dfe338"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":null,"outputs":[],"id":"ad928c13"},{"cell_type":"markdown","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**.","metadata":{},"id":"eebc1269"},{"cell_type":"markdown","source":"***\nWe have already seen that we have read/write access to entries of our vector with the **```[ ]```** operator. Another alternative is the **```at()```** method.\n\nThe difference is that **```at()```** will throw an exception, when we commit an out-of-bounds access error.","metadata":{},"id":"dc64b2c9"},{"cell_type":"code","source":"%%file exception.cpp\n\n#include <iostream>\n#include <vector>\n\nint 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}","metadata":{},"execution_count":null,"outputs":[],"id":"7e537a6b"},{"cell_type":"code","source":"!g++ -Wall -DOUT_OF_BOUNDS exception.cpp","metadata":{},"execution_count":null,"outputs":[],"id":"0513b187"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":null,"outputs":[],"id":"af593caa"},{"cell_type":"code","source":"!g++ -Wall exception.cpp","metadata":{},"execution_count":null,"outputs":[],"id":"131dd4f5"},{"cell_type":"code","source":"!./a.out","metadata":{},"execution_count":null,"outputs":[],"id":"e6ce23bd"},{"cell_type":"markdown","source":"### Iterators and range-based access","metadata":{},"id":"8e0987ec"},{"cell_type":"markdown","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\nand the same value $v$ can show up at multiple positions ($e_i = v = e_j$ with $i\\neq j$).\n\nAnother 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\nwhere\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 \nContrary to a vector, a list does not offer index-based access to its elements. Instead we can do list-traversal.\n\nNow consider the following piece of code:","metadata":{},"id":"80b33721"},{"cell_type":"code","source":"#include <iostream>\n#include <vector>\n\nint 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\nmain();","metadata":{},"execution_count":null,"outputs":[],"id":"eee3b34a"},{"cell_type":"markdown","source":"For what we are doing in the program the type of underlying container does not really matter. \nHow about replacing vector with list for a change?","metadata":{},"id":"33c83653"},{"cell_type":"code","source":"#include <iostream>\n#include <list> // okay, need to adapt the include\n\nint 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\nmain();","metadata":{},"execution_count":null,"outputs":[],"id":"d2806a4f"},{"cell_type":"markdown","source":"***\nNow that is, where **iterators** come into play. They abstract away the details of the underlying container.","metadata":{},"id":"40d19e9a"},{"cell_type":"code","source":"// %%file fubar.cpp\n\n#include <iostream>\n#include <vector>\n\nint 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}","metadata":{},"execution_count":null,"outputs":[],"id":"4a6ba619"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":null,"outputs":[],"id":"907c51a5"},{"cell_type":"markdown","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.","metadata":{},"id":"5b9766a5"},{"cell_type":"markdown","source":"Since our loop is using an iterator now, we can with two small modification switch from a **std::vector** to a **std::list**.","metadata":{},"id":"ba14f11e"},{"cell_type":"code","source":"#include <iostream>\n#include <list>\n\nint 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}","metadata":{},"execution_count":null,"outputs":[],"id":"9f8daf26"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":null,"outputs":[],"id":"53211059"},{"cell_type":"markdown","source":"#### 'Reverse mode' \nUp 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.","metadata":{},"id":"44f9f4e2"},{"cell_type":"code","source":"#include <iostream>\n\nstd::vector< short > vec;\n\nvec.push_back( 1 );\nvec.push_back( 2 );\nvec.push_back( 3 );\n\nfor( std::vector< short >::reverse_iterator it = vec.rbegin(); it != vec.rend(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{},"execution_count":null,"outputs":[],"id":"158950d1"},{"cell_type":"markdown","source":"#### 'Const mode'\nWhen we do not intend to change the element in the loop body, we can also employ a ```const_iterator```.","metadata":{},"id":"96bed53d"},{"cell_type":"code","source":"std::list< double > lc;\nusing cIter = std::list< double >::const_iterator;\nlc.push_back( 4.0 );\nlc.push_back( 5.0 );\nlc.push_back( 6.0 );\nfor( cIter it = lc.begin(); it != lc.end(); it++ ) {\n std::cout << *it << std::endl;\n}","metadata":{},"execution_count":null,"outputs":[],"id":"44a9bf28"},{"cell_type":"markdown","source":"### Range-based access\nC++ nowadays also supports and even simpler way to encode a loop over a container:","metadata":{},"id":"c731df99"},{"cell_type":"code","source":"#include <iostream>\n#include <list>\n\nint 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}","metadata":{},"execution_count":null,"outputs":[],"id":"2497a826"},{"cell_type":"code","source":"main();","metadata":{},"execution_count":null,"outputs":[],"id":"a8918a0e"},{"cell_type":"markdown","source":"Note that the last loop version involved a copy operation!","metadata":{},"id":"f4d8fd5e"},{"cell_type":"code","source":"std::cout << \"-> Put objects into container\" << std::endl;\nstd::vector< Dummy > vec;\nvec.reserve(2);\nvec.emplace_back( \"one\" );\nvec.emplace_back( \"two\" );\n\nstd::cout << \"\\n-> Run a loop\" << std::endl;\nfor( Dummy x: vec ) {\n std::cout << \"* \" << x.getName() << std::endl;\n}","metadata":{},"execution_count":null,"outputs":[],"id":"d1c64b51"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"28b2e3fb"}]} \ No newline at end of file +{ + "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> // required for setw() below\n", + "\n", + "int main() {\n", + "\n", + " const int n = 5;\n", + " \n", + " std::vector< int > vec; // need to tell compiler what we will\n", + " // 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 subscript 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": 3, + "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": 8, + "id": "5d4ba36f", + "metadata": {}, + "outputs": [], + "source": [ + " // Suggestions anyone?\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": 9, + "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", + "\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", + " *[That's what gave us \"Object 'one_copy_copy' was created\" above]*\n", + " 1. Destruction of objects in old data-array and its de-allocation. \n", + " *[That's what gave us \"Object 'one_copy' was destroyed\" above]*\n", + "* Afterwards the new entry can be appended.\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": [ + "***\n", + "This will also work for our original example with the class Dummy:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "a5dffa46-65b8-477b-bbd8-a0ad0801d1fb", + "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", + "\n", + "-> Auto clean-up at program end\n", + "Object 'one_copy' was destroyed\n", + "Object 'two_copy' was destroyed\n", + "Object 'two' was destroyed\n", + "Object 'one' was destroyed\n" + ] + } + ], + "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": 12, + "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_22: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": 21, + "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", + "\n", + " // Will fail, as we have no default constructor\n", + " // std::vector< Dummy > vec2( 2 );\n", + "\n", + " // vec.push_back( obj1 );\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": 22, + "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 'three' was created\n", + "\n", + "-> Auto clean-up at program end\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": 23, + "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": 24, + "id": "0513b187", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall -DOUT_OF_BOUNDS exception.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "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": 26, + "id": "131dd4f5", + "metadata": {}, + "outputs": [], + "source": [ + "!g++ -Wall exception.cpp" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "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 container 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": 28, + "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": 29, + "id": "d2806a4f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[1minput_line_33: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": 31, + "id": "4a6ba619", + "metadata": {}, + "outputs": [], + "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", + " // 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": 32, + "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 one, 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 modifications switch from a **std::vector** to a **std::list**." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9f8daf26", + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "#include <list> // mod #1\n", + "\n", + "int main() {\n", + " using Container = std::list< int >; // mod #2\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": 34, + "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 (if the container supports it)." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "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": 36, + "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 an even simpler way to encode a loop over a container:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "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 (in this case we can do w/o a ref)\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": 42, + "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": 43, + "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 +}