# PHAS0100 - Week 3 ### 2pm-5pm Friday 28th January :::info ## :tada: Welcome to the 3rd live-class! #### Reminder: both groups are now merged into this Friday class ### Today Today we will - Learn about: - The C++ Standard Library - Smart pointers and move semantics - Lambda expressions - Error handling with C++ exceptions - Work through some in-class and homework exercises that use these - 💻 :ok_hand: ::: ### Timetable for this afternoon | Start | End | Topic | | -------- | -------- | -------- | | 14:05 | 14:15 | Lecture part 1: C++ standard library | | 14:15 | 14:55 | Breakout/coding: Exercise 13 | | 15:55 | 15:05 | Break | --- | | 15:05 | 15:30 | Lecture part 2: Smart pointers | | 15:30 | 16:00 | Breakout/coding: Exercise 15 | | 16:00 | 16:10 | Break | --- | | 16:10 | 16:30 | Lecture part 3: Lambda expressions and error handling | | 16:30 | 16:50 | Breakout/coding: Exercise 16 | | 16:25 | 16:50 | Summary and closeout | Exercises 14, 17 and 18 are the homework exercises for this week. ## Breakout session 1 ## Class Exercise 13 * Write short code snippets that use: 1. Containers `<array>`, `<vector>` and `<map>` * First look them up on https://en.cppreference.com/w/cpp * Suggestion: create a `std::array` and `std::vector` containing `std::string` elements of colours `"Red"`, `"Green"`, `"Blue"` etc. What is the key difference between `std::array` and `std::vector`? Trying using a `std::map<std::string, unsigned int>` to count the number of times a colour occurs in a `std::array` or `std::vector` 3. The `<random>` number generator library * Look at the [reference information](https://www.cplusplus.com/reference/random/) for `<random>` and try creating 10 random numbers following a uniform distibution between 0.0 and 1.0; try creating random numbers following an exponential distribution; try to use auto to bind the generator and the distribution for repeated use 5. `<iostream>` to read in a string from the terminal and output it to screen * Hint: use `std::cin`, you can see an example in the [reference information](https://en.cppreference.com/w/cpp/io/cin) #### Solution <details> 1. For `<array>` take a look at the docs here: [https://en.cppreference.com/w/cpp/container/array](https://en.cppreference.com/w/cpp/container/array) The key features of `std::array` are that the type and length are fixed when you create an instance of the array template (you'll learn about templates in week 4). Some example snippets: ```cpp= // an array holding a set of colours std::array<std::string, 3> colours { \ "Red", "Blue", "Green"}; // the array is fixed in size when instantiated std::cout << colours[0] << " " \ << colours[1] << " " << colours[2] << std::endl; // will output: Red Blue Green // with an array you can change content of an existing element colours[2] = "Blue"; std::cout << colours[0] << " " \ << colours[1] << " " << colours[2] << std::endl; // will output: Red Blue Blue // as the array size is fixed you cannot add new elements, i.e. no // colours.push_back("Blue"); // would not compile, no push_back method // colours[4] = "Blue"; // will compile but will get runtime error ``` Now repeat but with a `<vector>`. Look at the documentation [https://en.cppreference.com/w/cpp/container/vector](https://en.cppreference.com/w/cpp/container/vector). ```cpp= // initialise vector with 3 elements std::vector<std::string> colours_vec { \ "Red", "Green", "Blue"}; // will contain "Red", "Green", "Blue", "Yellow" // then add a new element to the end colours_vec.push_back("Yellow"); // will now contain "Red", "Green", "Blue", "Yellow" // or you could remove the last element colours_vec.pop_back(); // will now contain "Red", "Green", "Blue" ``` Now for `<map>`. Again look at documentation: [https://en.cppreference.com/w/cpp/container/map](https://en.cppreference.com/w/cpp/container/map). Then create a map that can be used to store how often a particular colour occurs in the above `colours_vec`: ``` cpp= // maps can contain key-value pairs. Here we will use a map // to keep track of how often each colour occurs std::map<std::string, unsigned int> colour_counter; // loop over elements in colours for(auto colour : colours){ // if element does not exist set count to 1 if(colour_counter.count(colour) == 0){ colour_counter[colour] = 1; } else { // else increment by 1 colour_counter[colour] += 1; } } ``` 2. The `<random>` library was introduced in C++11 and should be used over the `rand` function from `<cstdlib>`. See the documentation [https://www.cplusplus.com/reference/random/](https://www.cplusplus.com/reference/random/) `rand` is fine for a quick demo but for anything that matters and where you rely on a pseudo random number generator for scientific results you should use `<random>` as it allows you to choose a generator (generally better options than `rand`) as well as a distribution to map the random numbers to - which if you implement yourself can often introduce bias. Some examples using `<random>`: ```cpp= // choose a generator std::default_random_engine generator; // default for testing // std::mt19937 generator; // or you can choose a specic algorithm // and a distibution for generated number to follow std::uniform_real_distribution<double> uniform(0.0,1.0); std::cout << "Generating 10 random doubles between 0.0-1.0:" << std::endl; for(int i = 0; i<10; i++){ std::cout << uniform(generator) << std::endl; } // or following an exponential e^-x std::exponential_distribution<double> exponential(1.0); std::cout << "Generating 10 random doubles following exponential e^-1/x:" << std::endl; for(int i = 0; i<10; i++){ std::cout << exponential(generator) << std::endl; } // if you are going to be reusing many times you // can bind the generator and distribution - std::bind // requires you to #include <functional> auto ran_exp = std::bind(exponential, generator); std::cout << "Generating some more:"<< std::endl; std::cout << ran_exp() << std::endl; std::cout << ran_exp() << std::endl; ``` 3. Example using `std::cin` ```cpp= std::string name; std::cout << "Enter your name?" << std::endl; std::cin >> name; int number; std::cout << "Enter your favourite number?" << std::endl; std::cin >> number; std::cout << "Hello, " << name << ", " << number << " is a good one!" << std::endl; ``` </details> ## Breakout session 2 ## Homework Exercise 14 _Don't attempt this in breakout it will take too long - if there is time in the breakout you can discuss steps if there are questions_ * Using the <random> library write a function to calculate Pi using Monte Carlo integration. * Hints: * Consider a square of length 2.0*2.0 containing a circle of radius 1.0 * Generate uniform random points within the square * Calculate the fraction of points that land in the circle * Then multiply this fraction by the area of the square * **Note: this serves as an example of MC integration but would be an inefficient way to calculate Pi** #### Solution <details> ```cpp= // Consider a 2*2 square containing a circle with radius 1.0 std::uniform_real_distribution<double> uniform_real(-1.0, 1.0); std::mt19937_64 mt64; auto ran_pos = std::bind(uniform_real, mt64); // Generate random points within the square unsigned int ntrials = 1000000; unsigned int inside = 0; // count hits inside circle double x,y; // create outside loop as will be re-using for(int i = 0; i < ntrials; i++){ x = ran_pos(); y = ran_pos(); if(std::pow(x*x + y*y, 0.5) < 1.0){ inside++; } } // Pi = area square * fraction of hits in circle std::cout << "Estimate of Pi "<< 2.0*2.0*(double)inside/(double)ntrials << std::endl; ``` </details> ## Class Exercise 15 In this exercise you will create an example to demonstrate why smart pointers are useful, discuss the key features of `std::unique_ptr`, `std::shared_ptr` and `std::weak_ptr` and if time look at an example of a problematic circular dependency with `shared_ptr`'s 1. Create a memory leak using bare pointers and `new` to heap allocate and then repeat using using smart pointers * Suggestion: use a short `Foo` class that has `std::cout` to screen when constructor and destructor are called 2. Discuss why smart pointers are useful? Discuss the key features of `shared_ptr` vs `unique_ptr` vs `weak_ptr`? 3. Write a function that returns a `unique_ptr` to a class and then some code to transafer ownership of that unique pointer using `std::move` 4. _IF THERE IS TIME OTHERWISE COMPLETE AS HOMEWORK_ Write a class that holds a shared pointer to other classes of that type as one of its data members: a `Person` class with a `best_friend` data member that is a `shared_ptr` to another `Person`, for example. Introduce a circular dependency: two people who are each others best friends. What are the consequences? How would you fix this? #### Solution <details> 1. This can be demonstrated with a simple dummy `Foo` class that prints out when its destructor is called: In `Foo.h`= ``` cpp #ifndef Foo_h #define Foo_h class Foo { public: Foo(); ~Foo(); }; #endif ``` and `Foo.cpp` ``` cpp #include "Foo.h" #include <iostream> Foo::Foo(){ std::cout << "In constuctor" << std::endl; } Foo::~Foo(){ std::cout << "In destructor" << std::endl; } ``` Then the memory leak code can go into the main app: ``` cpp= #include <iostream> #include <memory> #include "Foo.h" int main(){ std::cout << "Create Foo's in loop using raw pointers:" << std::endl; for(int i = 0; i < 5; i++){ Foo * f = new Foo(); // new instance on heap // ... // do some stuff // ... //delete f; // uncomment to prevent leak } std::cout << "Finished - now outside loop scope" << std::endl; std::cout << "\nCreate Foo's in loop using smart pointers:" << std::endl; for(int i = 0; i < 5; i++){ // Create an instance of Foo on the heap with new auto f = std::make_unique<Foo>(); // ... // do some stuff // ... } std::cout << "Finished - now loop scope" << std::endl; return 0; } ``` 2. Key points: * Smart pointers keep track of ownership of objects created on the heap (with `new`). They take care of freeing up memory when the last pointer that owns it goes out of scope: so you don't have to remember when to call delete on a pointer that is owned and don't accidentally call delete on one that is not owned. * `unique_ptr` models unique ownership. Only one pointer owns the object that it points to. No copy or assignment operators -> you have to use `std::move` to transfer ownership. * `shared_ptr` for when multiple smart pointers own the object they point to. Memory is only freed up when the last smart pointer to the object goes out of scope. Beware circular dependencies: A points to B and B points to A then neither will ever be deleted -> can lead to a memory leak. * `weak_ptr` Can point to an object created on the heap but doesn't own it. Object will be deleted once the last owner (shared_ptr) goes out of scope. When using a weak pointer you first have to check if it still points to an object as it doen't control the lifetime of that object. 3. An example of a function returning a `std::unique_ptr` to the Foo class from part 1: ``` cpp= std::unique_ptr<Foo> GetFoo(){ auto foo = std::make_unique<Foo>(); return foo; } ``` Then used as: ```cpp= auto foo1 = GetFoo(); std::cout << "before std::move foo1 -> "<< foo1.get() << std::endl; auto foo2 = std::move(foo1); std::cout << "after std::move:" << std::endl; std::cout << "foo1 -> "<< foo1.get() << std::endl; std::cout << "foo2 -> "<< foo2.get() << std::endl; ``` 4. First create a `Person` class that contains a `std::shared_ptr` to another `Person`: * The `Person.h` header file: ``` cpp= #ifndef Person_h #define Person_h #include <memory> class Person { public: Person(); ~Person(); std::shared_ptr<Person> best_friend; }; #endif ``` * The `Person.cpp` source file: ```cpp= #include "Person.h" #include <iostream> Person::Person(){ std::cout << "Constructing a person" << std::endl; } Person::~Person(){ std::cout << "Person destructor " << std::endl; } ``` * Then inside your code (in the main function or another function): ``` cpp= auto alice = std::make_shared<Person>(); auto bob = std::make_shared<Person>(); alice->best_friend = bob; bob->best_friend = alice; ``` * When running the code you will see the destructors are never called... ``` shell Constructing a person Constructing a person ``` * This is because we created a circular dependency: alice's best_friend points to bob and bob's best friend points to alice. There is no way for the `shared_ptr` reference count to go to zero. We can get around this by using a `weak_ptr` to store the best friend. In `Person.h` change to: ``` cpp std::weak_ptr<Person> best_friend; ``` * Then when you run the code you will see: ``` shell Constructing a person Constructing a person Person destructor Person destructor ``` * Once you have switched to a `weak_ptr` for `best_friend` you need to always check first if it points to anything (as it doesn't have ownership of the object) before using. The `weak_ptr::lock` method can be used to check the weak pointer still points to an object and convert to a shared_ptr is it does: ```cpp= if(auto best_friend = alice->best_friend.lock()){ std::cout << "Alice has a friend" << std::endl; // best_friend is now a shared_ptr pointer } else { std::cout << "Alice no longer has a friend" << std::endl; } ``` </details> ## Breakout session 3 ## Class Exercise 16 1. Create your own lambda expressions for each of the three basic syntax examples given below: ``` cpp [ captures ] { body }; [ captures ] ( params ) { body }; [ captures ] ( params ) -> ret { body }; ``` 2. Try to change a parameter passed to `(params)` from within, can you see a different behaviour if passed by reference or by value? 3. Use `std::count_if` with an appropriate lambda expression to count the number of values in a `vector<int>` that are divisible by 3 and greater than 6 * With input `std::vector<int> v{1,2,3,4,5,6,7,8,9,10,21};` the result of `count_if` should be 2 #### Solution <details> 1. Examples of the three ways to define a lambda expression: ``` cpp= double radius = 10.0; double pi = M_PI; // have to include <cmath> // from C++20 will be able to use std::pi from <numbers> // lambda expression of form [ captures ] { body } auto area = [pi, radius]{ return radius*radius*pi; }; std::cout << "area = "<< area() << std::endl; // outputs: area = 314.159 ``` ``` cpp= // this time captures pi from enclosing scope but radius passed // as a parameter auto area2 = [pi](double radius){ return radius*radius*pi; }; std::cout << "area2(1.0) = "<< area2(1.0) << std::endl; std::cout << "area2(3.0) = "<< area2(3.0) << std::endl; // outputs // area2(1.0) = 3.14159 // area2(3.0) = 28.2743 ``` ``` cpp= // as above but now specify that the return type is int auto area3 = [pi](double radius) -> int { return radius*radius*pi; }; std::cout << "area3(1.0) = "<< area3(1.0) << std::endl; std::cout << "area3(3.0) = "<< area3(3.0) << std::endl; // outputs // area3(1.0) = 3 // area3(3.0) = 28 ``` 2. You can only modify a capture/variable if it is passed by reference: ``` cpp= int one = 1; int two = 2; //auto increment = [one, two] { one++; two++; } // will give compile error // capture by reference and can then modify the captured variables auto increment = [&one, &two] {one++; two++; }; // before: one = 1, two = 2 increment(); // after: one = 2, two = 3 auto increment_this = [](int & input) { input++; }; increment_this(one); // after: one = 3, two = 3 // the following would give a compile error: read-only reference auto try_to_increment_this = [](const int & input) { input++; }; try_to_increment_this(one); ``` 3. Consider ```cpp= std::vector<int> v{1,2,3,4,5,6,7,8,9,10,21}; int n_counted = std::count_if(v.begin(), v.end(), \ [](auto element) -> bool { return element % 2 == 0 && element > 6; } ); std::cout << n_counted << std::endl; ``` </details> ## Homework Exercise 17 * Create a short `Student` class that has public member variables storing `string firstname`, `string secondname` and `int age` * Create a vector and fill it with various instances of the `Student` class * Use `std::sort` with a custom lambda expression to sort the vector of students by age #### Solution <details> The student class `Student.h` ``` cpp= #ifndef Student_h #define Student_h #include <string> class Student { public: Student(std::string firstname, \ std::string secondname, int age); ~Student(); std::string firstname; std::string secondname; int age; }; #endif ``` And `Student.cpp` ``` cpp= #include "Student.h" Student::Student(std::string firstname, \ std::string secondname, int age) { this->firstname = firstname; this->secondname = secondname; this->age = age; // do other stuff to setup } Student::~Student(){ // do stuff to cleanup } ``` Then in your main/application you could create a vector of students and sort them like this: ``` cpp= std::vector<Student> students; students.push_back(Student("John", "Doe", 30)); students.push_back(Student("Beatrice", "Potting", 29)); students.push_back(Student("Steve", "Bloggs", 21)); students.push_back(Student("Alice", "Wonder", 33)); // Then sort using a lambda expression for the comparitor std::sort(students.begin(), students.end(), \ [](auto a, auto b) -> bool { return a.age < b.age; } ); for(auto student : students){ std::cout << student.firstname << " " << student.secondname << " age " << student.age << std::endl; } // outputs: // Steve Bloggs age 21 // Beatrice Potting age 29 // John Doe age 30 // Alice Wonder age 33 ``` </details> ## Homework Exercise 18 In this exercise you will work through some examples of error handling with exceptions: 1. Implement a short try-catch block in your main app - it should throw and catch one of the C++ standard exceptions see [https://en.cppreference.com/w/cpp/error/exception](https://en.cppreference.com/w/cpp/error/exception) 2. Implement a function that throws an exception that is then caught in the calling scope where the function is called * Suggestion: follow the _Error Handling C++ Style_ example from the week 3 lecture notes 2. Implement a class that throws an exception from the constructor if invalid inputs are use when constructing the object: * Suggestion: use the Fraction class you developed in Exercise 8 and throw an exception if the denominator is 0 * Derive your own exception class from `std::exception` for this #### Solution <details> 1. Example main app to catch a `std::exception`: ```cpp= #include <iostream> #include <stdexcept> int main(){ try // code inside try block is monitored to see if exception thrown { // do some stuff if(1) // something exceptional happened { throw std::runtime_error("A runtime error occured"); } // continue to do stuff } catch (const std::runtime_error &e) // was a std::runtime_error thrown? { // if so deal with the exception here std::cout << "Caught exception: "<< e.what() << std::endl; } return 0; } ``` 2. Following the the `Error Handling C++ Style` example from the week 3 lecture notes you could create the following application. For simplicity here we define everying in a self contained `errorHandlingCppStyle.cpp` application file: ```cpp= #include <stdexcept> #include <iostream> int ReadNumberFromFile(const std::string& fileName) { if (fileName.length() == 0) { throw std::runtime_error("Empty fileName provided"); } // Check for file existence etc. throw io errors. // do stuff return 2; // returning dummy number to force error } void ValidateNumber(int number) { if (number < 3) { throw std::logic_error("Number is < 3"); } if (number > 10) { throw std::logic_error("Number is > 10"); } } int main(int argc, char** argv) { try { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " fileName" << std::endl; return EXIT_FAILURE; } int myNumber = ReadNumberFromFile(argv[1]); ValidateNumber(myNumber); // Compute stuff. return EXIT_SUCCESS; } catch (std::exception& e) // anything inheriting from std::exception { std::cerr << "Caught Exception:" << e.what() << std::endl; } } ``` * And then try running as: ```shell= ./build/errorHandlingCppStyle ./build/errorHandlingCppStyle "" ./build/errorHandlingCppStyle file.txt ``` 3. Will be uploaded soon. </details> # Questions Here you can post any question you have while we are going through this document. A summary of the questions and the answers will be posted in moodle after each session. Please, use a new bullet point for each question and sub-bullet points for their answers. For example writing like this: ``` - Example question - [name=student_a] Example answer - [name=TA_1] Example answer ``` produces the following result: - Example question - [name=student_a] Example answer - [name=TA_1] Example answer Write new questions below :point_down: - [] - [name=TA_1] Example answer ###### tags: `phas0100` `teaching` `class`