# PHAS0100 - Week 4 (3rd February 2023) ## Introduction to Modern C++ 3 This week we'll write some classes exploring a selection of design patterns, and make use of templates and function overloading to write generic code. ## Before You Begin **Please clone the exercise repository for this week and build your dev container before class.** You can [clone the repository here](https://github.com/UCL-PHAS0100-22-23/week4_cpp_exercises). Open the folder `week4_cpp_exercises`, which contains the `.devcontainer`, in VSCode and build the dev container. This is the folder you should have open in VSCode when doing all the exercises. You will move between the subfolders to compile individual exercises using the terminal in VSCode. **You can also work in the github codespaces for these exercises if your dev container is being unresponsive.** `CMakeLists.txt` files are provided for each folder. You can build each using the commands from inside the appropriate folder (`DependencyInjection`, `Templates`, or `RAII`): ```bash= cmake -B build cmake --build build ``` You will then find the executable in `build/bin` inside that folder. ## Timetable for this afternoon | Start | End | Topic | | -------- | -------- | -------- | | 14:00 | 14:15 | Introduction | | 14:15 | 14:50 | Section 1: Dependency Injection & Strategy Pattern | | 14:50 | 15:00 | Break | | 15:00 | 15:40 | Section 2: Templates | | 15:40 | 15:50 | Discussion: Run-time & Compile-time Polymorphism | | 15:50 | 16:00 | Break | | 16:00 | 16:40 | Section 3: RAII | | 16:40 | 16:50 | Coursework Announcement | ## Section 1: Dependency Injection & Strategy Pattern In this exercise we'll take a look at two common design patterns in one class: dependency injection and the strategy pattern. **In your terminal, go into the `DependencyInjection` folder.** Dependency injection is a way of separating the construction of an object and the construction of one of its components. We can set the component object as part of the main object by passing it into the constructor, or a setter function. In `FunctionSampler.h` and `FunctionSampler.cpp` you will find the definition of a `FunctionSampler` class. This class stores a function, and an object which can generate a set of points. The `FunctionSampler` has a method called `generateSamples` which uses the points generator object to generate a set of points, and then samples the function at these points and returns the result as a vector of doubles. Notice that the `FunctionSampler` creates the `LinearPointsGenerator` inside its constructor code. This means that the way that the `LinearPointsGenerator` is created is hard-coded inside the `FunctionSampler` class, and if the constructor for `LinearPointsGenerator` changes signature, then the `FunctionSampler` class will have to be changed as well. The definitions of these two classes are therefore *coupled*. ### Exercise 1: Constructor Injection Change the `FunctionSampler` class definition to make use of the _Dependency Injection_ pattern. 1. Re-write the constructor to allow the points generator to be passed in to the constructor instead. 2. In `DependencyInjection.cpp` rewrite the code in `main` to make use of your new constructor. ### Exercise 2: Setter Injection 1. Add a setter function for the `generator` variable that allows you to change the generator being used. 2. Add a default constructor which does not take a generator and does not set the `generator` variable. 3. Rewrite your main code to create a default `FunctionSampler` and then set the generator using your setter function. ### Exercise 3: Strategy Pattern The strategy pattern exploits run-time polymorphism to allow us to change the "strategy" used by a class during the program's execution. The "strategy" could be the implementation of anything handled by some helper class: in this case we will look at the points generator used by our `FunctionSampler` class. We have defined two classes, `LinearPointsGenerator` (which we have used so far and which generates linearly spaced points between the min and max) and `RandomPointsGenerator` (which generates uniform random points between min and max). We will use the strategy pattern to allow our class to change approach at runtime. 1. Define an abstract base class `SamplePointsGenerator` which contains `generatePoints` as a pure, virtual method. 2. Make `LinearPointsGenerator` and `RandomPointsGenerator` publicly inherit from this base class. 3. Modify your `FunctionSampler` class to use a pointer to the base class. 4. Go into your `main` function and write a program where you first generate a linearly spaced sample of your function, and then a random sample of your function, by changing the generator using your setter method. 5. Print both samples to the screen so that you can see that it is working. ### Exercise 4: Allowing someone to set the generator by passing in an arbitrary pointer introduces some risk into your class: the pointer being used could be empty / invalid, and accesses to invalid memory would result in an error such as a segmentation fault. - Try to guard against invalid pointers. - How does memory ownership affect the safety of using a pointer member variable? Consider this when selecting what kind fo pointer you are using. - Can you prevent `FunctionSampler` objects from being constructed with uninitialised generator pointers? ## Section 2: Templates & Function Overloading In these exercises we'll explore function templates and class templates. We'll start by writing a simple function template and making use of operator overloading to apply it to a number of different types. **In your terminal, go into the `Templates` folder.** ### Exercise 1: In `Polynomial.h` and `Polynomial.cpp` there is a simple function `quadratic` which calculates $ax^2 + bx + c$ for integer inputs `a`, `b`, `c`, and `x`. If we were to rewrite this function for `double`, `float`, `uint`, and many others, it would look exactly the same except for the parameter / return types. This is therefore an ideal candidate for a template! 1. Rewrite the `quadratic` function as a template function to generalise over the parameter/return type. - Separate your declaration and defintion between the header and cpp file just like normal. 2. Write explicit instantiations for types `int` and `double` in the `Polynomial.cpp` file. - See the bottom of [the section on organising and compiling code with templates](https://github-pages.ucl.ac.uk/research-computing-with-cpp/04cpp3/sec02Templates.html) if you're not sure how to do this. 3. In `Templates.cpp`, make calls to both the `int` version and the `double` version and print out the results to check that they both work. 4. In `Templates.cpp` add the following line: `double d_quad = quadratic(1.4, 2, 3.9, 0.1);`. This uses _implicit instantiation_ i.e. we have not given the compiler the precise type that we want in `<>` but have left it to the compiler to infer. Try to recompile and work out why it fails. - Hint: consider why `double d_quad = quadratic(1.4, 2.0, 3.9, 0.1);` does pass instead, and what assumptions the compiler is making about the types of numbers. 5. Fix this line by using explicit instantiation instead. ### Exercise 2: We've provided another implementation of the `Fraction` class from last week. This version includes a `Simplify` method, and also `Multiply` and `Add` methods which multiply or add together two fractions. In fact, being able to multiply and add is all that we need to meet the requirements of our templated polynomial function! In this exercise we'll overload the `*` and `+` operators for our fraction class, and show that we can then use this with our template. 1. In `Fraction.hpp` add declarations to overload `operator*` and `operator+`. You can find examples of this [in the notes on "Using Templates with Overloaded Functions"](https://github-pages.ucl.ac.uk/research-computing-with-cpp/04cpp3/sec02Templates.html). 2. In `Fraction.cpp` add definitions for these operators. - Overloading the `*` and `+` operators allow us to define fractions `f1` and `f2`, and then write expressions like `f1+f2` instead of `f1.Add(f2)`. This will match what is in our `quadratic` function, and other arithmetic functions! 3. Rather than adding yet another instantation to `Polynomial.cpp`, let's make our template a bit more flexible. Remove the definition of `quadratic` from `Polynomial.cpp` and place it directly in the header `Polynomial.h` instead. - This will make the full template definition available in anything that includes the header file, allowing other parts of the code to generate instantiations for other types without them having to be explicitly instantiated ahead of time in the `Polynomial.cpp` file. - Putting templated code directly in the header file is very common, and the basis of (and motivation for) many header-only libraries. Header only libraries put _all_ their code directly in header files instead of `cpp` files, and thus the libraries are not separately compiled and linked. This is usually to take advantage of heavy templating in the code. 4. Add code to your `main` function to apply your quadratic function using `Fraction` types and print the result. - We have overloaded the `<<` operator for you so that you can use `Fraction` with `cout`. E.g. for a `Fraction f`, we can write `std::cout << f << std::endl`. ### Exercise 3 It's now time to try writing a class template instead. Write a class to represent an $N \times M$ matrix with elements of arbitrary type in `MatrixTemplate.h`. (You can make this header only for the sake of making the template easy to use.) Try to consider the following points as you design your class: - How should you store your data for your matrix? Is you solution memory safe? - How should data be accessed? What should be public and private? - How should you initialise your matrix? - What functionality should a matrix class have? - Will your template work with `int` and `double`? - Will your template work with `Fraction`? If not, what other functionality would you need to overload `Fraction` to make it usable with your matrix template? Use your class in `main` in `Templates.cpp` to create a matrix and test some of your functionality. ## Section 3: Resource Acquisition Is Initialisation (RAII) In this section we'll explore the RAII pattern in the context of file access. We'll start with a pre-written example in an old-school C style approach, and update this by wrapping our file access in a class implementing RAII, and look at how files are accessed in modern C++. **In your terminal, go into the `RAII` folder.** ### Exercise 1: The focus will be on a function called `printProcessedFile`, which opens a file and reads data from it, passes each piece of data into a function (in this case `factorial`) and prints it out, and then finally closes the file again. 1. Take a look at `RAII.cpp`. Initially, we have a C-style approach to opening and reading a file (with some C++ exceptions thrown added in). Let's go through the steps that it takes to open, read, and close the file in : - The file is opened with [`std::fopen`](https://en.cppreference.com/w/cpp/io/c/fopen). If this fails (the pointer is null) then we throw an exception. (Note that `std::fopen` itself does not throw any exceptions, but reports an error through the value of the pointer returned.) - The file that we will read is called `testFile.txt`, and contains integers separated by whitespace. - Each piece of data is read individually using `std::fscanf`, and passed to the `factorial`, until we reach the end of the file `EOF`. - The file is closed with `std::fclose`. 2. Under what circumstances might the file be _opened_ but not _closed_? - Hint: Check the `factorial` function. 3. Modify the `testFile.txt` so that an exception is thrown by `factorial` during `printProcessedFile` if it encounters an invalid value which cannot be processed by `factorial`. Compile and run the program and check the output to see that we indeed have not closed the file. (Look out for the `file opened` and `file closed` couts.) - If we lose access to the file handle before we close the file, then we won't be able to close it! ### Exercise 2: - Write a class called `FileManager` in `FileManager.h` and `FileManager.cpp`. This class should implement RAII by meeting the following conditions: 1. Resources should be acquired (i.e. the file should be opened) in the constructor. - The constructor should throw an exception if opening the file fails. 2. Resources should be released (i.e. the file should be close) in the destructor. - **Be careful that you don't try to close a file that isn't open as this will cause a seg fault.** This is similar to trying to delete a pointer which is not allocated. - Add `cout` statements to the constructor and destructor to report when the file is opened and closed. - You should also add a function `getData` which reads a piece of data from the file. - Rewrite `printProcessedFile` to use your `FileManager` class instead of handling file opening and closing directly. Test that it works, and that the file is closed _even if an exception is thrown by_ `factorial`. ### Exercise 3: As we've already seen with smart pointers (which implement a kind of RAII for pointers to heap memory), modern C++ provides tools to handle this kind of resource management for us for many common situations. Indeed, similar to smart pointers, there are file reading classes in standard C++ which implements RAII for us: `fstream` (file I/O), `ofstream` (output), and `ifstream` (input). Best practice is to use these existing RAII-implementing structures where available (since these will be reliable, well tested solutions) and build your own RAII implementing classes when they are not. You should now modify `printProcessedData` to use `ifstream` (since we are only reading and not writing data). 1. Add an include for `<fstream>` to `RAII.cpp`. 2. In `printProcessedFile`, replace your `FileManager` with `std::ifstream`. The constructor for `ifstream` takes a `std::string` representing the filename `testFile.txt`. 3. The loop is very similar but uses the condition: `while(filestream >> n)`, where `filestream` is your `ifstream` variable. - This places the next piece of data in the file into the variable `n` and evaluates to true if it was not the end of file marker. - You can then pass `n` to the factorial function inside the loop as usual. Re-compile and run your code to test that it still works.