# PHAS0100 - Week 2 ### Group 2 is 10am-1pm Monday 17th January ### Group 1 is 2pm-5pm Friday 21st January :::info ## :tada: Welcome to the 2nd live-class! ### Today Today we will - have a lecture-style recap of C++ features - try out some coding exercises using `auto` and range based for loops - have a lecture-style overview of Object Oriented programming - try implementing a shape class - plus look at inheritance and polymorphism for homework - 💻 :ok_hand: ::: ### Timetable for Monday Group 2 session <details> | Start | End | Topic | | -------- | -------- | -------- | | 10:05 | 10:25 | Lecture part 1: C++ standards, auto, range based for loops | | 10:25 | 10:55 | Breakout/coding session 1: Exercise 9 | | 10:55 | 11:05 | Break | --- | | 11:05 | 11:25 | Breakout/coding session 2 | | 11:25 | 11:55 | Lecture part 2: Object Oriented programming | | 11:55 | 12:05 | Break | --- | | 12:05 | 12:45 | Breakout/coding session 3: Exercise 11 | | 12:45 | 12:55 | Lecture part 3: Summary and closeout | </details> ### Timetable for Friday Group 1 session <details> | Start | End | Topic | | -------- | -------- | -------- | | 14:05 | 14:25 | Lecture part 1: C++ standards, auto, range based for loops | | 14:25 | 14:55 | Breakout/coding session 1: Exercise 9 | | 14:55 | 15:05 | Break | --- | | 15:05 | 15:30 | Breakout/coding session 2 | | 15:30 | 15:55 | Lecture part 2: Object Oriented programming | | 15:55 | 16:05 | Break | --- | | 16:05 | 16:45 | Breakout/coding session 3: Exercise 11 | | 16:50 | 16:55 | Lecture part 3: Summary and closeout | </details> ## Breakout session 1 ## Class Exercise 9 * Clone [https://github.com/UCL/CMakeLibraryAndApp](https://github.com/UCL/CMakeLibraryAndApp) * Create a new library to perform various operations on a `std::vector<int>` 1. Using a range base for loop Write a function that prints all the elements on the vector to screen and call this from `myApp` 2. Write a function using a range based for loop that counts the number of elements equal to a value 5 3. Now repeat but instead use the `std::count` algorithm from the `<algorithm>` library * cppreference is a good place to start to look up details of libraries/language features https://en.cppreference.com/w/cpp/algorithm 1. Optional if you have time: convince yourself C++ is statically typed despite using `auto` with following snippet: ```cpp auto number = 1; auto word = "sksksk"; word = number; #### Solution <details> * The aims here are to: * Try using a range-based for loop and `auto` for automatic type inference * Check you can write a function and call it from `myApp` * Then implement the same function but now using an existing algorithm `std::count` and see it is less code * Start with [https://github.com/UCL/CMakeLibraryAndApp](https://github.com/UCL/CMakeLibraryAndApp) 1. Implement a `PrintAll` function as part of the`CMakeLibraryAndApp/mpPrinting.cpp` library ```cpp void PrintAll(const std::vector<int> & v) { for (auto& element : v) { std::cout << element << " "; } std::cout << '\n'; } ``` 2. Which could then be used in myApp as: ``` cpp std::vector<int> v{1,32,8,4,16,64,2}; mp::PrintAll(v); ``` * A function to count elements equal to a 5 ```cpp int CountElements(const std::vector<int>& v, int value) { int n_match = 0; for (auto& element : v) { if(element == value) n_match++; } return n_match; } ``` 3. Now using std::count from ``<algorithm>`` ```cpp int CountElements(const std::vector<int>& v, int value) { return std::count(v.begin(), v.end(), value); } ``` * [https://en.cppreference.com/w/cpp/algorithm](https://en.cppreference.com/w/cpp/algorithm) docs * Note: you could use a lambda expression for this but this is not covered until week 4 4. If time: think about auto type deduction and the fact that C++ is still statically typed - types are deduced and fixed at compile time - you just let the compiler figure it out for you. Can you test this? ```cpp auto number = 1; auto word = "sksksk"; word = number; ``` You should see a compile error - it is not possible to dynamically change the type of `word` once declared. </details> ## Breakout session 2 ## Class Exercise 10 * Again starting from [https://github.com/UCL/CMakeLibraryAndApp](https://github.com/UCL/CMakeLibraryAndApp) * Write a function `void AddElements(vector<int> v, int val, int ntimes)` that takes a `vector<int>` and appends `ntimes` new elements with value `val` 1. Using `std::cout` print to screen the contents of the the input vector: before and after calling the function as well as from within the `add_elements` function itself, what is the problem? 2. Try passing by reference `add_elements(vector<int> &v, ...` instead, does it work now? 3. What are the advantages/disadvantages to passing by reference? 4. Try passing as a `const` reference `add_elements(const vector<int> &v, ...`, what is the behaviour now and why would you use this? #### Solution <details> * The aims here are to: * Show that if you pass a vector by value to a function and modify the contents inside the function then the original vector will be unchanged * Compared to passing by reference - they can be changed * Compared to passing by const reference - they can't be changed * As a general rule pass by const reference: efficient (no copy made) and safe as the function being called promises not to modify the variable * You should again start with [https://github.com/UCL/CMakeLibraryAndApp](https://github.com/UCL/CMakeLibraryAndApp) 1. If you implement AddElements as: ``` cpp void AddElements(std::vector<int> v, int val, \ unsigned int n_times) { for(unsigned int i = 0; i < n_times; i++) { v.push_back(val); } } ``` * And call from the app as: ``` cpp std::vector<int> v1{1,2,3,4}; std::cout << "\nBefore add elements called:" << std::endl; mp::PrintAnyType(v1); std::cout << "After add elements called:" << std::endl; mp::AddElements(v1, 1, 5); mp::PrintAnyType(v1); ``` * You will see from the output that the contents of vector `v1` in the enclosing scope are unchanged ``` Before add elements called: 1 2 3 4 After add elements called: 1 2 3 4 ``` * This is expected as `void AddElements(std::vector<int> v, ...`) passed the vector by-value so `v` is a copy of `v2` and changing `v` by adding more elements has no effect on `v2`. 2. If you instead pass by-reference using `void AddElements(std::vector<int>& v, ...)` you will see that `v2` is now altered after the call to AddElements: ``` Before add elements called: 1 2 3 4 After add elements called: 1 2 3 4 1 1 1 1 1 ``` 3. Discuss advantages/disadvantages: * **Passing by-value**: * A copy is made, can be expensive if the object is large * Not possible to modify the original variable from within the function * Maybe this is desired: if you need to manipulate but not effect the original * **Passing by-reference**: * You have access to the original variable (a reference to it) from within the function * No copy is made, which is good if you’re passing a large object or small objects many times * This means you can alter the value of the original variable from within the function * Again, can be an advantage or disadvantage * You can use a const reference if you do not expect the original variable to be changed * There is no such thing as a NULL reference and you cannot reassign which object the reference is referencing * **Passing a pointer**: * No copy is made, you can dereference the pointer to access the original variable * Can be set to NULL * Can reassign the address that the pointer points to * Lots that can go wrong including memory leaks - in general try to avoid and use smart pointers if needed 4. Passing as const reference - will give a compile error if try to modify the variable in the function. Useful if you want to pass something efficiently but also guarantee it will not be changed by the function * **In general use const reference unless the function is going to modify the variable and a reference if the function needs to modify the variable.** </details> ## Breakout session 3 ## Class Exercise 11 Use [https://github.com/UCL/CMakeCatch2](https://github.com/UCL/CMakeCatch2) and create a simple shape class for a square with methods to calculate the area 1. Create an object of the class from within an app and print the result of the area calculation to screen 2. Try to write some unit tests to check the class behaves as expected #### Solution <details> Aims: * Implement a class with no inheritance * Example of `#ifndef` include guards * Difference between declarations in header `.h` file and definitions in source .cpp file * Try some more unit tests Steps: * Start with [https://github.com/UCL/CMakeCatch2](https://github.com/UCL/CMakeCatch2) * Create a`Code/Lib/geomSquare.h` header file where the class is declared: ```cpp= #ifndef geomSquare_h #define geomSquare_h namespace geom { class Square { public: Square(const double &width = 0.0); ~Square(); double GetArea(); void SetWidth(const double &width); private: double m_width; }; } // end geom namespace #endif ``` * With corresponding implementation in `Code/Lib/geomSquare.cpp` source file where functions are defined: ``` cpp #include "geomSquare.h" #include <iostream> namespace geom { Square::Square(const double &width): m_width(width){ // can initialise/setup anything here } Square::~Square(){ // can cleanup here } double Square::GetArea(){ return m_width*m_width; } void Square::SetWidth(const double &width){ m_width = width; } } // end namespace ``` * Then after adding geomSquare.cpp to the `MYPROJECT_LIBRARY_SRCS` variable in `Code/Lib/CMakeLists.txt` will be able to call from `Code/CommandLineApps/mpMyFirstApp.cpp` as ``` cpp geom::Square square; std::cout << "Default square area " << square.GetArea() << std::endl; square.SetWidth(3.0); std::cout << "Square of width 3.0 area " << square.GetArea() << std::endl; ``` * And with an example unit test file: ``` cpp #include "catch.hpp" #include "mpCatchMain.h" #include "geomSquare.h" #include <iostream> #include <vector> TEST_CASE( "Area calculations", "[square]" ) { geom::Square square; REQUIRE( square.GetArea() == Approx(0.0) ); // N.B. use Approx to compare floating point numbers (doubles/floats) square.SetWidth(1.0); REQUIRE( square.GetArea() == Approx(1.0) ); square.SetWidth(1); REQUIRE( square.GetArea() == Approx(1.0) ); square.SetWidth(10.0); REQUIRE( square.GetArea() == Approx(100.0) ); square.SetWidth(3.2); REQUIRE( square.GetArea() == Approx(10.24) ); square.SetWidth(0.0); REQUIRE( square.GetArea() == Approx(0) ); } TEST_CASE( "Non-default constructor", "[square]" ) { geom::Square square1(0.0); REQUIRE( square1.GetArea() == Approx(0.0) ); geom::Square square2(10.0); REQUIRE( square2.GetArea() == Approx(100.0) ); } ``` * What about testing for failure: consider trying to construct a Square with a negative width? * Ideally the constructor would throw an exception (more on this later on in the course) and you could then test that an exception is thrown with `REQUIRE_THROW` </details> ## Homework Exercise 12 Again using CMakeCatch2 as a basis use inheritance and polymorphism to create a shape base class and a set of derived classes for a square and rectangle * Plan your base and derived classes first * Is the "Is-A" condition for derived classes satisfied? * Read through [this tutorial](https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/) on Universal Modelling Language * Next draw an inheritance diagram before starting to implement your code - see the tutorial and tools here uml-class-diagram-tutorial * If you don't want to draw it by hand you can use a free tool like [Creatily](https://creately.com/lp/uml-diagram-tool/) * Implement your design and confirm that the get area functions behave differently for the different dervied classes (i.e. polymorhism) * Now implement another derived class, a circle class #### Solution <details> * Again using CMakeCatch2 as a starting project, use inheritance and polymorphism to create a shape base class and a set of derived classes for a square, rectangle * Consider the "Is-A" condition: a Circle is-a type of Shape, a Square is-a type of Shape * In code that makes use of Shape objects you could replace with all instances of Shape with Circle or Square without changing the intended properties of the program * Let’s plan these classes using a UML diagram * See tutorial here [uml-class-diagram-tutorial](https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/) * You could draw by hand but the following was made using [Creatily](https://creately.com/lp/uml-diagram-tool/) _[not a recommendation, just the first free one I could find]_ ![](https://i.imgur.com/ftNbJD0.png) * Some notes on conventions: * Italics denote an abstract class (one or more pure virtual methods) or a pure virtual method * a `+`, `-`, `#` indicate `public`, `private` and `protected` data members and class methods respectively * The dashed lines indicate that `Circle` and `Rectangle` are derived classes of `Shape` where the arrow points to the base class * One way to remember this is with the nemonic “The arrow goes from the one that knows” i.e. `Circle` and `Rectangle` have to know (`#include`) about `Shape` but `Shape` does not need to know about `Circle` and `Rectangle` * A solid arrow indicates that the derived class provides a concrete implementation for the abstract base class (provide definitions of pure virtual methods) * Then the `Shape.h` abstract base class header file contains ```cpp #ifndef Shape_h #define Shape_h class Shape { public: Shape(){}; virtual double getArea() = 0; // = 0 makes it pure virtual virtual double getPerimeter() = 0; // also pure virtual std::string getName(){ return m_name; } protected: std::string m_name; }; #endif ``` * A class with at least 1 pure virtual function cannot be instantiated (you can't make create an instance of it) and is known as an abstract base class * And the `Circle.h and Shape.cpp` concrete derived class that inherits from `Shape.h` and implements the pure virtual functions - no pure virtual functions left so can be created/instaniated ``` cpp #ifndef Circle_h #define Circle_h #include "Shape.h" class Circle : public Shape { public: Circle(const double &radius); virtual double GetArea(); virtual double GetPerimeter(); void SetRadius(const double &radius); private: double m_radius; }; #endif ``` * And the implementation file: ``` cpp #include "Circle.h" #define _USE_MATH_DEFINES #include <cmath> // required for M_PI Circle::Circle(const double &radius): m_radius(radius){ m_name = "Circle"; } double Circle::GetArea(){ return M_PI*m_radius*m_radius; } double Circle::GetPerimeter(){ return 2*M_PI*m_radius; } void Circle::SetRadius(const double &radius){ m_radius = radius; } ``` * `Rectangle.h` ``` cpp #ifndef Rectangle_h #define Rectangle_h #include "Shape.h" class Rectangle : public Shape { public: Rectangle(const double &width, const double &height); virtual double GetArea(); virtual double GetPerimeter(); void SetWidth(const double &width); void SetHeight(const double &height); private: double m_width; double m_height; }; #endif ``` * `Rectangle.cpp` ``` cpp #include "Rectangle.h" Rectangle::Rectangle(const double &width, const double &height): m_width(width), m_height(height) { m_name = "Rectangle"; } double Rectangle::GetArea(){ return m_width*m_height; } double Rectangle::GetPerimeter(){ return 2*(m_width + m_height); } void Rectangle::SetWidth(const double &width){ m_width = width; } void Rectangle::SetHeight(const double &height){ m_height = height; } ``` * Then in another library or in the application you could write a function that operates on the Shape base class but calls the relevant derived class function when passed a pointer or reference to a derived class (polymorphism) ``` cpp // A method that takes a reference to Shape void PrintShapeInfo(Shape &shape){ std::cout << shape.GetName() << " with area " << shape.GetArea() << " and perimeter " << shape.GetPerimeter() << " and ratio area/perimeter " << shape.GetArea()/shape.getPerimeter() << "\n"; } ``` * Can then be used for any class that derived from Shape and called in the app as ``` cpp Rectangle r(10.0, 3.0); PrintShapeInfo(r); Circle c(5.0); PrintShapeInfo(c); ``` * You only have to write one function that can handle multiple shapes. This is a trivial example but the behaviour it demonstrates is very powerful </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`