# PHAS0100 - week 4 ### 2pm-5pm Friday 4th February :::info ## :tada: Welcome to the 4th live-class! #### Reminder: both groups are now merged into this Friday class ### Today Today we will learn about - Dependency injection and programming to interfaces - The RAII pattern: aquire resources in constructor, - Templates: function template, class templates, template specialisation - 💻 :ok_hand: ::: ### Timetable for this afternoon | Start | End | Topic | | -------- | -------- | -------- | | 14:05 | 14:35 | Lecture part 1: Dependency injection, program to interfaces, RAII | | 14:35 | 14:55 | Breakout/coding: Excercise 19 | | 15:55 | 15:05 | Break | --- | | 15:05 | 15:30 | Breakout/coding: Excercise 19 (cont) and exercise 20 | | 15:30 | 15:55 | Lecture part 2: Function and class templates | | 15:55 | 16:05 | Break | --- | | 16:05 | 16:45 | Breakout/coding: Exercise 21 | | 16:45 | 16:55 | Summary and closeout | Exercises 22 and 23 are the homework exercises for this week. ### Project templates <details> - [CMakeHelloWorld](https://github.com/UCL/CMakeHelloWorld) - Single `.cpp` file - [CMakeLibraryAndApp](https://github.com/UCL/CMakeLibraryAndApp) - Multiple `.h` and `.cpp` files - [CMakeCatch2](https://github.com/UCL/CMakeCatch2) - Includes Catch, example library (Eigen) and separate compilation of library and application </details> ## Breakout session 1 ## Class Exercise 19 In this session you will be trying out the two types of dependency injection: constructor dependency injection and setter dependency injection. * Building on the `Student` class from homework 17 ([Solution](https://hackmd.io/c0qe_VL1Qk2tSfaYDkgkyw?view#Solution4)): * Create a new `Laptop` class that has a string `os` data member for the operating system name and an integer `year` data member for the year produced. * `Laptop` should have both a default constructor that sets `year` to 0 and name to "Not set" as well as a constructor that takes two arguments to set `year` and `os` * Modify `Student` to have a `std::unique_ptr` to a `Laptop` as a private data member * Implement the two types of dependency injection for the `Student` class: constructor, setter * Confirm that the `Student` class is now invarient to changes in how you instantiate `Laptop` * Discuss how and why you might implement an abstract interface class for the types of objects a Student owns _This exercise may spill over into breakout 2. Class exercise 20 is quicker_ #### Solution <details> You could use CMakeLibraryAndApp as a starting point. The Laptop class should have multiple ways in which it can be constructed or configured. Here we just provide a default constructor (no arguments) and a constructor that allows `os` and `year` to be set. The `Laptop` class `Laptop.h` header file: ```cpp= #ifndef Laptop_h #define Laptop_h #include <string> class Laptop { public: Laptop(); Laptop(const std::string& os, const int& year); ~Laptop(); void SetOSYear(const std::string& os, const int& year); void Print(); private: std::string os_; int year_; }; #endif ``` `Laptop.cpp` implementation file: ```cpp= #include "Laptop.h" #include <iostream> //-------------------------------------------------------------------- Laptop::Laptop() { os_ = "Not set"; year_ = 0; // do other stuff to setup } //-------------------------------------------------------------------- Laptop::Laptop(const std::string& os, const int& year) { os_ = os; year_ = year; // do other stuff to setup } //-------------------------------------------------------------------- Laptop::~Laptop(){ // do stuff to cleanup } //-------------------------------------------------------------------- void Laptop::SetOSYear(const std::string& os, const int& year) { os_ = os; year_ = year; } //-------------------------------------------------------------------- void Laptop::Print() { std::cout << os_ << " ("<< year_ << ")" << std::endl; } ``` The `Student` class has a dependency on the `Laptop` class as has a data member pointing to a `Laptop` instance. We pass (inject) this dependency into the `Student` class. The Student class needs a constructor that accepts a pointer (we use a unique_ptr to avoid using raw pointers, see Week 3) to an already created Laptop instance to demonstrate constructor dependency injection. As well as a setter method to pass a Laptop pointer to demonstrate setter dependency injection. Student class `Student.h` header file: ```cpp= #ifndef Student_h #define Student_h #include "Laptop.h" #include <memory> #include <string> class Student { public: Student(std::string name, int age); Student(std::unique_ptr<Laptop> laptop, \ std::string name, int age); ~Student(); void SetLaptop(std::unique_ptr<Laptop> laptop); void Print(); private: std::string name_; int age_; std::unique_ptr<Laptop> laptop_; }; #endif ``` And the `Student.cpp` implementation file: ```cpp= #include "Student.h" #include <utility> // std::move #include <iostream> //-------------------------------------------------------------------- Student::Student(std::string name, int age) { name_ = name; age_ = age; // do other stuff to setup } //-------------------------------------------------------------------- Student::Student(std::unique_ptr<Laptop> laptop, std::string name, int age) { laptop_ = std::move(laptop); name_ = name; age_ = age; } //-------------------------------------------------------------------- Student::~Student() { // do stuff to cleanup } //-------------------------------------------------------------------- void Student::SetLaptop(std::unique_ptr<Laptop> laptop) { laptop_.swap(laptop); // laptop_ = std::move(laptop); } //-------------------------------------------------------------------- void Student::Print() { std::cout << name_ << ", "<< age_ << ", "; if(laptop_){ laptop_->Print(); } } ``` The following `myStudentApp.cpp` application then demonstrates the different types of dependency injection: ```cpp= #include "Student.h" #include "Laptop.h" #include <memory> #include <utility> int main(){ // Constructor dependency injection auto windows = std::make_unique<Laptop>("Windows 10 Home", 2020); Student juliet(std::move(windows), "Juliet", 101); juliet.Print(); // Setter dependency injection auto mac = std::make_unique<Laptop>("OSX", 2019); Student john("John", 100); john.SetLaptop(std::move(mac)); // note move required as it is a unique_ptr john.Print(); // Construct/configure Laptop differently auto ubuntu = std::make_unique<Laptop>(); ubuntu->SetOSYear("Ubuntu 20.04", 2021); // No change to Student.cpp/.h required Student jerome(std::move(ubuntu), "Jerome", 102); // Constructor dependency injection jerome.Print(); return 0; } ``` Example `CMakeLists.txt` lines for above if working from CMakeLibraryAndApp: ```cmake= set (CMAKE_CXX_STANDARD 17) add_library(Student Student.cpp) add_library(Laptop Laptop.cpp) add_executable(myStudentApp myStudentApp.cpp) target_link_libraries(myStudentApp Student Laptop) ``` Main points for discussion: * If you used programming to interfaces and defined an abstract interface class `ResourceI` with methods like `virtual int GetYear() = 0;` and `virtual int GetName() = 0;` * You could then have concrete derived classes of different types of resources like `Laptop` and `Book` and `Video` stored in a container of `std::vector<std::unique_ptr<ResourceI> >` in the `Student` class. * And implement general methods in `Student` to add resources and print a summary of all resources, for example. * The implementation of `Student` would not need to be changed even you create new concrete derived types of `ResourceI`. </details> ## Breakout session 2 ## Class Exercise 20 In this session you will implement a container class following the RAII (Resource Aquisition Is Initialisation) pattern: all resources aquired in the constructor and all resources released in the destrurctor. * Create a simple class `FooAllocator` that contains a data member that is a raw pointer `foo_ptr` to another class `Foo` * Add a `std::cout` to the constructor and destructor of both classes so that you know when they have been called * Create function `void AFunctionThatThrows()` that creates a `FooAllocator` object and then throws an exception before reaching the end of the fuction * Confirm that the destructor for both the `FooAllocator` and `Foo` objects are called as long as the exception is caught in the calling code * Discuss how you would implement using`std::unique_ptr` instead of a raw pointer? * i.e. how would the implementation differ if FooAllocator stored a `std::unique_ptr`? What if FooAllocator stored another type of resource, for example a `std::ofstream` for writing to files? #### Solution <details> For simplicity implement in a single `myRAIIExample.cpp` application file: ```cpp= #include <iostream> #include <stdexcept> using std::cout; using std::endl; // Dummy Foo class to indicate when constructed/destroyed class Foo { public: Foo(){ cout << "Foo()" << endl; } ~Foo(){ cout << "~Foo()" << endl;} }; // FooAllocator implement RAII for a Foo class FooAllocator { public: FooAllocator() // acquire resource in constructor { cout << "FooAllocator()" << endl; foo_ptr_ = new Foo(); // just an example, in general use smart pointers and avoid new } ~FooAllocator() // release resource in destructor { cout << "~FooAllocator()" << endl; delete foo_ptr_; } private: Foo * foo_ptr_; }; // A function that creats a FooAllocator and then throws exception void FunctionThatThrows(){ FooAllocator f; throw std::runtime_error("Something is wrong"); } // Simple main to show the Foo's destructor is called int main(){ try { FunctionThatThrows(); } catch (std::exception& e){ std::cout << "Caught exception: "<< e.what() << std::endl; } return 0; } ``` And then add the following to your CMakeLists.txt: ```cmake add_executable(myRAIIExample myRAIIExample.cpp) ``` Main points for discussion: * The FooAllocator implements similar behaviour to `std::unique_ptr` * If you were to store the `Foo` data member as a `std::unique_ptr<Foo>` in the FooAllocator or other class that owns it then you would not need to call the destructor `delete foo_ptr_` in ~FooAllocator * If the resource you are using implements RAII then no need to explicitly call destructor in class using it - this is the case for `std::ofstream` </details> ## Breakout session 3 In this session you'll try defining and instantiating some of your own function templates and class templates: trying out implicit vs explicit instantiation and working through an example where you provide a template specialisation for a specific type. ## Class Exercise 21 * Review the general syntax for a function template: `template <typename T> T sum(T a, T b);` * Then implement the `template <typename T> T sum(T a, T b);` example from the notes (slide number 31): * Try out both explicit and implicit instantiation #### Solution <details> The general form for a template function is `template < parameter-list > function-declaration` * The `template` keyword tells the compiler we are declaring or defining a template * The parameter list names the types that the template will use - you can use the `typename` or `class` keywords when defining a type although we recommend `typename` to avoid confusion with class definitions * An example template parameter would be`typename T1`. Here the name `T1` is arbitrary. You can think of it as an alias for a future type that will be supplied to the template function when it is instantiated * Then the `function-declaration` declares the function to be templated - this function can treat `T1` as if it is a type: it could manipulate variables of type T1, return a type T1, take type T1 variables as arguments Take the `sum` function template from the Week 4 slides. The following should go in a header `.h` file and it defines a `sum` function and the logic to sum two inputs together based on a generic type `T` that will be supplied later on when the template is instantiated: ```cpp template <typename T> // typename|class T sum (T a, T b) { T result; result = a + b; return result; } ``` which could then be instantiated in the main application as: ```cpp double a = 1.5; double b = 2.0; // implicitly instantiates sum<double>(double a, double b) std::cout << sum(a, b) << std::endl; // outputs 3.5 // explicitly instantiates sum<int>(static_cast<int>(a), static_cast<int>(b)) std::cout << sum<int>(a, b) << std::endl; // outputs 3 ``` * Here we can see that the logic in the `sum` function can be reused for any variable or type as long as the `+` operator is defined for it </details> ## Homework Exercise 22 _If there is time in the class can make a start on this_ Write a template function `TLists RemoveMatching(TElement element, TList l)` that searches a vector or list for elements matching `element` and returns a new `TList` with those elements removed * Try out both explicit and implicit instantiation * Write a template specialisation for `RemoveMatching` for the case where `TElement` is a `char` and `TList` is a `std::string` #### Solution <details> The following template definitions could go in a `RemoveMatching.h` header file that is then `#include RemoveMatching.h`'d in your main app: ```cpp // Returns a new list with all matching elements removed template<typename TElement, typename TList> TList RemoveMatching(TElement match, TList list) { TList new_list; for(auto element : list){ if(element != match) { new_list.push_back(element); } } return new_list; } // For convenience also define a Print template function template<typename T> void Print(T array) { for(auto entry : array) { std::cout << entry << " "; } std::cout << "\n"; } ``` Then some examples of how to instantiate the function template are: ```cpp std::vector<int> v{1,20,40,50}; // Implicit instantiation auto v2 = RemoveMatching(20, v); Print(v2); // outputs: 1 40 50 // Explicit instantiation auto v3 = RemoveMatching<int, std::vector<int>>(40, v); print(v3); // outputs: 1 20 50 std::list<std::string> l{"A","BB","CCC","DDD"}; // Implicit instantiation auto l2 = RemoveMatching("CCC", l); Print(l2); // outputs A BB DDD // Explicit instantiation for std::list but now try to pass // it a vector of int's - the following will not compile // auto l3 = RemoveMatching<std::string, std::list<std::string> >(20, v); ``` </details> ## Class Exercise 23 * Review the general syntax for a class template: `template < parameter-list > class-declaration` * Implement the `MyPair` class template from the week 4 slides * Try using with both Implicit and Explicit instantiation #### Solution <details> Compare the general syntax for a class template: `template < parameter-list > class-decaration` to the specific `MyPair` example from the notes (Week 4, slide number 45): In `pairClassExample.h` ```cpp template <typename T> class MyPair { T m_Values[2]; public: MyPair(const T &first, const T &second); T getMax() const; }; #include "pairClassExample.cc" // include .cc file in header file just for templates ``` * Note the similarities with the function template syntax: `template < parameter-list >` followed by function or class declaration. Then in `pairClassExample.cc` ```cpp template <typename T> MyPair<T>::MyPair(const T& first, const T& second) { m_Values[0] = first; m_Values[1] = second; } template <typename T> T MyPair<T>::getMax() const { if (m_Values[0] > m_Values[1]) return m_Values[0]; else return m_Values[1]; } ``` Then in the main app: ```cpp // Explicit instantiation MyPair<int> a(1,2); std::cout << "Max is:" << a.getMax() << std::endl; // outputs 2.0 // Implicit instantiation MyPair b(3.0, 4.5); std::cout << "Max is:" << b.getMax() << std::endl; // outputs 4.5 // Explicit <int> but now passing double that get static_cast MyPair<int> c(1.9,2.9); std::cout << "Max is:" << c.getMax() << std::endl; // outputs 2 ``` </details> ### Homework Exercise - 24 Extend the MyPair class with two new methods: * A `void printValues()` method that prints to screen the typeid of the class T which for which the template has been instantiated as well as the values of m_Values[0] and m_Values[1] * A `void swapValues()` method that uses `std::swap` to swap the elements #### Solution <details> The `pairClassExample.h` would have the following methods declarations added: ``` cpp template <typename T> class MyPair { T m_Values[2]; public: MyPair(const T &first, const T &second); T getMax() const; void swapValues(); void printValues(); }; #include "pairClassExample.cc" ``` and `pairClassExample.cc` has the following definitions added: ```cpp= template <typename T> void MyPair<T>::swapValues() { std::swap(m_Values[0], m_Values[1]); } template <typename T> void MyPair<T>::printValues() { std::cout << typeid(T).name() << ": " << m_Values[0] << " " << m_Values[1] << std::endl; } ``` then in the main function you could check with: ```cpp MyPair b(3.0, 4.5); b.printValues(); // outputs "d: 3 4.5" MyPair<int> c(1.9,2.9); c.printValues(); // outputs "i: 1 2 c.swapValues(); c.printValues(); // outputs "i: 2 1" ``` </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`