diff --git a/notebooks/lecture2.ipynb b/notebooks/lecture2.ipynb index 2e93745..a4dc6b2 100644 --- a/notebooks/lecture2.ipynb +++ b/notebooks/lecture2.ipynb @@ -1 +1 @@ -{"metadata":{"celltoolbar":"Slideshow","kernelspec":{"display_name":"C++17","language":"C++17","name":"xcpp17"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"code","source":"//-----------------------------Run this cell before the lecture-----------------------------\n#include \n#include ","metadata":{"tags":[]},"execution_count":null,"outputs":[],"id":"7a24648b"},{"cell_type":"markdown","source":"# Object-oriented scientific programming with C++\n\nMatthias Möller, Jonas Thies (Numerical Analysis, DIAM)\n\nLecture 2","metadata":{"slideshow":{"slide_type":"slide"}},"id":"876d5165"},{"cell_type":"markdown","source":"

Task: Dot product

\nWrite a $\\mathrm{C}++$ code that computes the dot product\n$$\na \\cdot b=\\sum_{i=1}^n a_i b_i\n$$\nof two vectors $a=\\left[a_1, a_2, \\ldots, a_n\\right]$ and $b=\\left[b_1, b_2, \\ldots, b_n\\right]$ and terminates if the two vectors have different length.","metadata":{"slideshow":{"slide_type":"slide"}},"id":"59398614"},{"cell_type":"markdown","source":"

Dot product function

\n\nThe main functionality without any fail-safe checks","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"00de1935"},{"cell_type":"code","source":"double dot_product(const double* a, int n, const double* b, int m)\n{\n double d=0.0;\n for (auto i=0; i
Dot product function - improved version
\n\nFirst version of the dot product with exception","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"6bfb7f00"},{"cell_type":"code","source":"#include \n#include \n\ndouble dot_product(const double* a, int n, const double* b, int m)\n{\n if (a == nullptr || b == nullptr) {\n // Handle null pointers by throwing an exception\n throw std::invalid_argument(\"Null pointer argument\");\n }\n\n if (n != m) {\n // Handle mismatched sizes by throwing an exception\n throw std::invalid_argument(\"Array sizes mismatch\");\n }\n\n // Core functionality\n double d = 0.0;\n for (int i = 0; i < n; ++i) {\n d += a[i] * b[i];\n }\n return d;\n}","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"77d96a85"},{"cell_type":"code","source":"double x[5] = { 1, 2, 3, 4, 5 };\ndouble y[4] = { 1, 2, 3, 4 };\ntry {\n double d = dot_product(x, 5, y, 4);\n} catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"tags":[]},"execution_count":null,"outputs":[],"id":"a56e3e96-c884-4b16-aeb9-fbf84fddc800"},{"cell_type":"markdown","source":"It would be much better if `x` and `y` would \"know\" their length internally so that the calling function cannot provide inconsistent data","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"b8e06e52"},{"cell_type":"markdown","source":"

Cool! But what was the reason I enrolled in this course?

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"ac26729d"},{"cell_type":"markdown","source":"

Flashback: Object Oriented Programming

\n

Imagine each LEGO block as a piece of data (like an integer, string, etc.). A struct or a class is like a LEGO set that contains a variety of different blocks.

\n
    \n
  • Main idea of OOP is to bundle data (e.g. array) and\nfunctionality (e.g. length) into a struct or class\n
  • \n
  • Components of a struct are public (=can be accessed from\noutside the struct) by default\n
  • \n
  • Components of a class are private (=cannot be accessed\nfrom outside the class) by default\n
  • \n
  • Components of a struct/class are attributes and member\nfunctions (=methods)\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5a0bb6d1"},{"cell_type":"markdown","source":"

Class vs. struct

\n
\nstruct Vector {\n public: //default\n double* array;\n int length;\n private:\n};\n
\n
\nclass Vector {\n private: //default\n public:\n double* array;\n int length;\n};\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"cb3fdc9a"},{"cell_type":"code","source":"class Vector {\n private: //default\n public:\n double* array;\n int length;\n};","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"35917d43-877b-46d7-972c-532cfb921af1"},{"cell_type":"markdown","source":"

When to use class and when to use struct?

\n
  • struct is typically used when you want to group data together without needing to restrict access to it. It is straightforward and simple. Many type traits (later in thios course) are implemented as structs.\n
  • \n
  • class is typically used when you want more control over the data and the interface through which it is accessed and manipulated, promoting the principles of encapsulation and data hiding.\n
  • \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"25f6d0fa"},{"cell_type":"markdown","source":"

Dot product as a member function of Vector

\n\nSecond version of the dot product using Vector class or struct","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5fb04b49"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector{\n public:\n double* array;\n int length;\n \n double dot_product(const Vector& a, const Vector& b)\n {\n if(a.length != b.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; istruct or class by dot-notation („.“)","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"9065c92c"},{"cell_type":"markdown","source":"Is the above implementation really OOP? How would you invoke it from the main program?","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"id":"21d0748b-421f-49c9-859f-eaa81085eec2"},{"cell_type":"code","source":"Vector x,y;\nx.array = new double[5]; x.length = 5;\ny.array = new double[5]; y.length = 5;\nfor (int i = 0; i < 5; ++i) {\n x.array[i] = i + 1; // 1, 2, 3, 4, 5\n y.array[i] = i + 1; // 1, 2, 3, 4, 5\n}\n \ntry {\n double result = /** ??? **/;\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"execution_count":null,"outputs":[],"id":"2756e20a-199a-40dd-b041-714151eb5308"},{"cell_type":"markdown","source":"The current implementation of the dot_product function comes from functional programming and is not OOP style because it takes two input arguments `x` and `y` and returns one output argument, the dot product. In other words, it is not attached to any object (`x` or `y`).","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"id":"3df90be7-9c6c-4cc3-bbf7-f68c6bd7bc65"},{"cell_type":"markdown","source":"If we want to implement a function inside a class or struct that is not attached to an object we have to define it as static","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"d23d2ba6-1233-4569-86cb-7472c77fbd74"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector{\n public:\n double* array;\n int length;\n \n double dot_product(const Vector& a, const Vector& b)\n {\n if(a.length != b.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; istatic member functions are invoked with the full classname (comparable to namespaces)","metadata":{},"id":"f5bbb4da-b866-4f1d-9578-f851f6215557"},{"cell_type":"code","source":"Vector x,y;\nx.array = new double[5]; x.length = 5;\ny.array = new double[5]; y.length = 5;\nfor (int i = 0; i < 5; ++i) {\n x.array[i] = i + 1; // 1, 2, 3, 4, 5\n y.array[i] = i + 1; // 1, 2, 3, 4, 5\n}\n \ntry {\n double result = Vector::dot_product(x, y);\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"767a8d52-d3fa-4ce2-90c5-5cab12bdf449"},{"cell_type":"markdown","source":"
    \n
  • It is still possible to initialise x.length by the wrong value, e.g.,\n
  • \n

    x.array = new double[5] {1, 2, 3, 4, 5}; x.length = 4;

    \n
  • The main function is not very readable due to the lengthy\ndeclaration, initialisation and deletion of data\n
  • \n
  • OOP solution:\n
  • \n
      \n
    • Constructor(s): method to construct a new Vector object\n
    • \n
    • Destructor: method to destruct an existing Vector object\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"412bd54a"},{"cell_type":"markdown","source":"

Constructor

\n\nThe constructor is called each time a new Vector object (=instance of the class Vector) is created","metadata":{"slideshow":{"slide_type":"slide"}},"id":"276ae022"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"7783126e"},{"cell_type":"markdown","source":"A class can have multiple constructors if they have a different interface (=different parameters)","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"ba2b6b0e"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n \n Vector(int len) // Another constructor\n {\n array = new double[len];\n length = len;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"0a655a26"},{"cell_type":"markdown","source":"What if a parameter has the same name as an attribute?","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"1ef93da1"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n \n Vector(int length) // Another constructor\n {\n // this pointer refers to the object itself,\n // hence this->length is the attribute and length\n // is the parameter passed to the constructor\n\n array = new double[length];\n this->length = length;\n }\n}; ","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"4034efd6"},{"cell_type":"markdown","source":"

Destructor

\n\nThe destructor is called implicitly at the end of the lifetime of a Vector object, e.g., at the end of its scope","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5708f6a6"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n ~Vector() // Destructor (and there can be only one!)\n {\n delete[] array;\n length = 0;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"ae4c1e5d"},{"cell_type":"markdown","source":"Cleaning up the main program with constructors and (implicitly invoked) destructors\n\nint main(){\n Vector x; // Default constructor is called\n {\n Vector y(5); // Constructor is called\n // Destructor is called for Vector y\n }\n // Destructor is called for Vector x\n }\n\nWithout array = nullptr in the default constructor the destruction of x will lead to a run-time error.","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"4133f84f"},{"cell_type":"markdown","source":"

Uniform initialisation constructors (C++11)

\n\nRemember this\ndouble x[5] = { 1, 2, 3, 4, 5 };\n\nIt would be cool to simply write\nVector x = { 1, 2, 3, 4, 5 };\n\n\nC++11 solution: initializer lists (#include <initializer_list>) and a special copy function (#include <memory>) \n\nVector(const std::initializer_list<double>& list) {\n length = (int)list.size();\n array = new double[length]; \n std::uninitialized_copy(list.begin(),\n list.end(), array);\n}","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"cf7475d7"},{"cell_type":"code","source":"class Vector {\nprivate:\n double* array;\n int length;\n\npublic:\n // Constructor with initializer list\n Vector(const std::initializer_list& list)\n {\n length = (int)list.size();\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n }\n \n // Destructor\n ~Vector() {\n delete[] array;\n }\n};","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"execution_count":null,"outputs":[],"id":"e9ecf2c0"},{"cell_type":"markdown","source":"

Dot product – close to perfection

\n\nThird version of the dot product using Vector class with uniform initialisation constructor (C++11) and exceptions\n\nint main()\n{\n Vector x = { 1, 2, 3, 4, 5 };\n Vector y = { 2, 4, 6, 8, 10};\n try {\n double dot_product(x, y);\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n }\n}","metadata":{"slideshow":{"slide_type":"slide"}},"id":"afd5b6f8"},{"cell_type":"markdown","source":"

Delegating constructor (C++11)

\nCan we delegate some of the work, e.g., array = new double[length];","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"c0b1a899"},{"cell_type":"code","source":"class Vector {\nprivate:\n double* array;\n int length;\n\npublic:\n Vector(int length)\n {\n this->length = length;\n array = new double[length];\n }\n \n Vector(const std::initializer_list& list)\n {\n length = (int)list.size();\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n } \n} ","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"e900f59b"},{"cell_type":"markdown","source":"Delegating constructors delegate part of the work to\nanother constructor of the same or another class\n\nVector(int length) \n : length(length), array(new double[length])\n{ }\n\nHere, delegation is not really helpful but more a question\nof coding style, e.g., some programmers use delegation in\nall situation where this is technically possible\n\nIt is no longer necessary to distinguish between the\nattribute (this->length) and the argument (length) if both\nhave the same name. But be careful with the order in\nwhich delegated objects are constructed!","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"310e8ce7"},{"cell_type":"markdown","source":"

Quiz

\n
\n
    \n
  • Vector(int len)\n : length(len),\n array(new double[len])\n {}
  • \n
  • Vector(int len)\n : array(new double[len]),\n length(len)\n {}\n
  • \n
\n
\n
\n
    \n
  • Vector(int len)\n : length(len),\n array(new double[lengh])\n {}
  • \n
  • Vector(int len)\n : array(new double[lenth]),\n length(len)\n {}\n
  • \n
\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5378508f"},{"cell_type":"markdown","source":"If you have multiple constructors with increasing functionality, delegating constructors can be really helpful to remove duplicate code, e.g.\n\nVector(const std::initializer_list<double>& list)\n: Vector((int)list.size())\n{\n std::uninitialized_copy(list.begin(),\n list.end(), array);\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"0fd11e67"},{"cell_type":"markdown","source":"

Function -> member function

\n\nFunction that computes the sum of a Vector\n\nstatic double sum(const Vector& a)\n{\n double s = 0;\n for (auto i=0; i<a.length; i++)\n s += a.array[i];\n return s;\n}\n\nThis is not really OOP-style!\n\nint main() {\n Vector x = { 1, 2, 3, 4, 5 };\n std::cout << sum(x) << std::endl; \n}\n","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"58ab6c05"},{"cell_type":"markdown","source":"Implementation of sum as an OOP-style member function","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"b6a9be11"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector {\n private:\n double* array;\n int length;\n \n public:\n Vector(const std::initializer_list& list) {\n length = static_cast(list.size());\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n }\n \n ~Vector() {\n delete[] array;\n }\n \n double sum() {\n double s = 0;\n for (int i = 0; i < length; i++) {\n s += array[i];\n }\n return s;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"938ea41b"},{"cell_type":"markdown","source":" This is good OOP-style\n\nVector v = {1.0, 2.0, 3.0};\nstd::cout << v.sum() << std::endl;","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"fe1a3a38"},{"cell_type":"markdown","source":"Can we implement the dot_product function as a member function?","metadata":{"slideshow":{"slide_type":"slide"}},"id":"ccf5de20"},{"cell_type":"code","source":"class Vector {\n private:\n double* array;\n int length;\n \n public:\n double dot_product(const Vector& other) {\n if (length != other.length)\n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; iint main()\n{\n Vector x = {1,2,3}; Vector y = {2,4,6};\n std::cout << x.dot_product(y) << std::endl;\n std::cout << y.dot_product(x) << std::endl;\n}
\n\nFormally, the dot product is an operation between two\nVector objects and not a member function of one Vector\nobject that needs another Vector object for calculation","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"a16793a3"},{"cell_type":"markdown","source":"

Operator overloading

\nC++ allows to overload (=redefine) the standard operators\n\n- Unary operators: `++a`, `a++`, `--a`, `a--`, `~a`, `!a`\n- Binary operators: `a+b`, `a-b`, `a*b`, `a/b`\n- Relational operators: `a==b`, `a!=b`, `ab`, `a>=b`\n\nInterfaces:\n\n\nreturn_type operator() [const]\nreturn_type operator(const Vector& other) [const]

\n \n[Complete list:](https://en.cppreference.com/w/cpp/language/operators)https://en.cppreference.com/w/cpp/language/operators","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"c70addbd"},{"cell_type":"markdown","source":"Implementation of dot product as overloaded *-operator\n\ndouble operator*(const Vector& other) const\n {\n if (length != other.length)\n throw std::invalid_argument(\"Vector lengths mismatch\");\n double d=0.0;\n for (auto i=0; i<length; i++)\n d += array[i]*other.array[i];\n return d;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"257182ca"},{"cell_type":"markdown","source":"Now, the dot product is implemented as *-operator that\nmaps two Vector objects to a scalar value\n\nint main()\n{\n Vector x = {1,2,3}; Vector y = {2,4,6};\n std::cout << x * y << std::endl;\n std::cout << y * x << std::endl;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"d5960faa"},{"cell_type":"markdown","source":"The const specifier indicates that the Vector reference other must not be modified by the *-operator\n\nThe trailing const specifier indicates that the this pointer (aka, the object whose function is invoked) must not be modified by the *-operator\n\ndouble operator*(const Vector& other) const { ... }","metadata":{"tags":[],"slideshow":{"slide_type":"subslide"}},"id":"6719164c-c1f7-498a-822c-5bb7fb8e0d92"},{"cell_type":"markdown","source":"

Assignment by operator overloading

\n\nImplementation of assignment as overloaded =-operator\n\nVector& operator=(const Vector& other)\n{\n if (this != &other)\n {\n length = other.length;\n delete[] array;\n array = new double[length];\n for (auto i=0; i<length; ++i)\n array[i] = other.array[i];\n }\n return *this;\n}\n\n- Usage: Vector x, y; x = y;\n- Note that the this pointer is modified so there must not be a trailing const","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"37e996c0"},{"cell_type":"markdown","source":"Implementation of incremental assignment as overloaded =-operator\n\nVector& operator+=(const Vector& other)\n{\n if(length != other.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n for (auto i=0; i<length; i++)\n array[i] += other.array[i];\n return *this;\n}\n\n- Usage: Vector x, y; x += y;\n- Note that the this pointer is modified so there must not be a trailing const","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"9242b8b7-6046-4ad1-8f2f-16d8d737f44b"},{"cell_type":"markdown","source":"

Container class

","metadata":{"slideshow":{"slide_type":"skip"},"tags":[]},"id":"6de0c1bd"},{"cell_type":"code","source":"#include \n#include \n#include \n\nclass Container {\nprivate:\n int size;\n double* elements;\n\npublic:\n // Default constructor\n Container() : size(0), elements(nullptr) {\n std::cout << \"Default constructor called\" << std::endl;\n }\n\n // Converting constructor: can also initialize Container with an int\n Container(int length) : size(length), elements(new double[length]) {\n std::cout << \"Converting constructor (from int) called\" << std::endl;\n std::fill(elements, elements + size, 0.0);\n }\n\n // Converting constructor: can also initialize Container with an initializer_list\n Container(const std::initializer_list& list) \n : size(static_cast(list.size())), elements(new double[size]) {\n std::cout << \"Converting constructor (from initializer_list) called\" << std::endl;\n std::copy(list.begin(), list.end(), elements);\n }\n\n // Explicit constructor to prevent implicit conversions from a single float\n explicit Container(float initial_value) : size(1), elements(new double[1]) {\n std::cout << \"Explicit constructor called\" << std::endl;\n elements[0] = initial_value;\n }\n\n // Copy constructor\n Container(const Container& other) : size(other.size), elements(new double[other.size]) {\n std::cout << \"Copy constructor called\" << std::endl;\n std::copy(other.elements, other.elements + size, elements);\n }\n\n // Move constructor\n Container(Container&& other) noexcept : size(other.size), elements(other.elements) {\n std::cout << \"Move constructor called\" << std::endl;\n other.size = 0;\n other.elements = nullptr;\n }\n\n // Destructor\n ~Container() {\n std::cout << \"Destructor called\" << std::endl;\n delete[] elements;\n }\n\n // Copy assignment operator\n Container& operator=(const Container& other) {\n std::cout << \"Copy assignment operator called\" << std::endl;\n if (this != &other) { // Protect against self-assignment\n delete[] elements; // Free the existing resource.\n size = other.size;\n elements = new double[size];\n std::copy(other.elements, other.elements + size, elements);\n }\n return *this;\n }\n\n // Move assignment operator\n Container& operator=(Container&& other) noexcept {\n std::cout << \"Move assignment operator called\" << std::endl;\n if (this != &other) { // Prevent self-assignment\n delete[] elements; // Free the existing resource.\n size = other.size;\n elements = other.elements;\n other.size = 0;\n other.elements = nullptr;\n }\n return *this;\n }\n\n // A simple print function to show the contents\n void print() const {\n for (int i = 0; i < size; ++i) {\n std::cout << elements[i] << \" \";\n }\n std::cout << std::endl;\n }\n};\n\nint main() {\n Container defaultContainer; // Calls default constructor\n\n Container fromInt = 5; // Calls converting constructor with an int\n fromInt.print();\n\n Container fromList = {1.0, 2.0, 3.0}; // Calls converting constructor with an initializer_list\n fromList.print();\n\n // The line below will cause a compile-time error due to the 'explicit' keyword\n // Container fromFloat = 2.5f; // Error\n Container fromFloat(2.5f); // Calls explicit constructor\n fromFloat.print();\n\n Container copyContainer = fromList; // Calls copy constructor\n copyContainer.print();\n\n // Assigning a new value to copyContainer using copy assignment operator\n copyContainer = fromInt;\n copyContainer.print();\n\n Container moveContainer; // Calls default constructor\n moveContainer = std::move(copyContainer); // Calls move assignment operator\n moveContainer.print();\n\n return 0;\n}","metadata":{"tags":[],"slideshow":{"slide_type":"skip"}},"execution_count":null,"outputs":[],"id":"c7111714"},{"cell_type":"markdown","source":"

Container class

\nclass Container {\nprivate:\n double* data;\n int length;\npublic:\n Container(int length)\n : length(length), data(new double[length])\n { }\n Container(const std::initializer_list<double>& l)\n : Container( (int)l.size() )\n {\n std::uninitialized_copy(l.begin(), l.end(), data);\n }\n};","metadata":{"slideshow":{"slide_type":"slide"}},"id":"6bcfdf28"},{"cell_type":"markdown","source":"

Conversion constructors

\n\nBoth constructors convert a single input argument into a Container object, hence, they are called conversion constructors. They can be called in two different ways\n
    \n
  • Using the regular construction form
  • \nContainer a( 4 );\nContainer a( {1,2,3,4} );\n
  • Using copy initialisation
  • \nContainer a = 4; // -> Container a( 4 )\nContainer a = {1,2,3,4}; // -> Container a( {1,2,3,4} )\nContainer a = {4}; // which constructor is called?\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"41cb11de"},{"cell_type":"markdown","source":"

Explicit specifier

\n\nThe explicit specifier prevents the use of the constructor as conversion constructor\n\nexplicit Container(int length) \n : length(length), data(new double[length])\n { }\n}\n\nNow, copy-initialisation (Container a = 4;) is no longer possible\nbut explicit constructor (Container a( 4 );) has to be used","metadata":{"slideshow":{"slide_type":"slide"}},"id":"9da21506"},{"cell_type":"markdown","source":"

Constructors summary

\n\n\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n
ConstructorDescriptionUsage
DefaultConstructor with no parameters.Used to create an object with default values.
ParameterizedConstructor with parameters to initialize an object with specific values.Used to create an object with specified attributes.
CopyA constructor that initializes an object using another object of the same class.Used to create a copy of an object.
ExplicitConstructor with the explicit keyword to prevent implicit conversions or copy-initialization.Used to enforce explicit object creation with constructor.
\n\n","metadata":{"slideshow":{"slide_type":"slide"}},"id":"f41a401c"},{"cell_type":"markdown","source":"

Task: Numerical integration

\n
    \n
  • Approximate a one-dimensional integral by numerical quadrature
  • \n $$\\int_a^bf(x)dx \\approx \\sum_{i=1}^n \\omega_i f\\left(x_i\\right)$$\n
  • Choice of quadrature weights $w_i$ and points $x_i$ determines\n the concrete numerical integration rule
  • \n
\n ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a8d2812d"},{"cell_type":"markdown","source":"

Simple integration rules

\n
    \n
  • Midpoint rule\n
  • \n $$\\int_a^b f(x) d x \\approx(b-a) \\cdot f\\left(\\frac{a+b}{2}\\right)$$\n
  • Simpson rule\n
  • \n $$\\int_a^b f(x) d x \\approx \\frac{b-a}{6}\\left[f(a)+4 f\\left(\\frac{a+b}{2}\\right)+f(b)\\right]$$\n
  • Rectangle rule\n
  • \n $$\\int_a^b f(x) d x \\approx h \\sum_{n=0}^{N-1} f\\left(x_n\\right), \\quad h=\\frac{b-a}{N}, \\quad x_n=a+n h$$\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"84a5ecaf"},{"cell_type":"markdown","source":"

Gauss integration rules

\n
\n
    \n
  • Zoo of Gauss integration rules with quadrature weights and points tabulated for the reference interval [-1,1]\n
  • \n
  • Complete list of weights/points is available, e.g., at Wikipedia\n
  • \n
\n
\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
$n$$\\xi_{i}$$w_i$
102
2-0.577350269192
0.577350269191
3-0.7745966692415/9
0.08/9
0.7745966692415/9
4-0.8611363115940530.347854845137454
-0.3399810435848560.652145154862546
0.7745966692410.652145154862546
0.8611363115940530.347854845137454
\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"9bcf5ae9"},{"cell_type":"markdown","source":"
    \n
  • Change of variable theorem\n
  • \n $$\\int_{a}^{b} f(x)dx = \\int_{-1}^{1}f(\\phi(t))\\phi^{i}(t)dt$$\n
  • Mapping from interval [a,b] to interval [-1,1]\n
  • \n $$\\phi(t) = \\frac{b-a}{2}t + \\frac{a+b}{2}, \\phi^{'}(t) = \\frac{b-a}{2}$$\n
  • Numerical quadrature rule\n
  • \n $$\\int_{a}^{b}f(x)dx\\approx\\phi^{'}\\sum_{n=1}^{n}w_if(\\phi(\\xi_i))$$\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"c1e5486d"},{"cell_type":"markdown","source":"

Program Design

\n
    We need...\n
  • A strategy to ensure that all numerical quadrature rules\n(=classes) provide an identical interface for evaluating integrals\n
  • \n
  • A standard way to pass user-definable function f(x) from outside (=main routine) to the evaluation function\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a5f156a5"},{"cell_type":"markdown","source":"

Program Design

\n
    We need...\n
  • A strategy to ensure that all numerical quadrature rules (=classes) provide an identical interface for evaluating integrals\n
  • \n
      \n
    • Polymorphism: Base class Quadrature provides common attributes and member functions (at least their interface declaration); derived classes implement specific quadrature rule (reusing common functionality of the base class, where this is possible and makes sense)\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"e3ca4745"},{"cell_type":"markdown","source":"

Program Design

\n
    We need...\n
  • A standard way to pass user-definable function f(x) from outside (=main routine) to the evaluation function\n
  • \n
      \n
    • Function pointers (traditional approach)\n
    • \n
    • Lambda expressions (recommended approach since C++11)\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"7adb6a72"},{"cell_type":"markdown","source":"

Function Pointers

\n
    \n
  • Define a function to be integrated\n
  • \n const double myfunc1(double x)\n{\nreturn x; }\n
  • Define interface of the integrate function\n
  • \n double integrate(const double (*func)(double x),\n double a, double b)\n{\n // do the numerical integration\n}\n
  • Usage:integrate(myfunc1,0,1);\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"be00d9e9"},{"cell_type":"markdown","source":"

Lambda Expressions

\n
    \n
  • Introduced in C++11, lambda expressions provide an elegant way to write user-defined callback functions\n
  • \n
  • General syntax\n
  • \n auto name = [<captures>] (<parameters>) {<body>};\n
  • Lambda expressions can be inlined (anonymous functions)\n
  • \n integrate([<captures>](<parameters>) {<body>});\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"97851d03"},{"cell_type":"markdown","source":"

Lambda Expressions

\n
    \n
  • Define function to be integrated\n
  • \n auto myfunc2 = [](double x) { return x; };\n
  • Define interface of the integration function\n
  • \n double integrate(std::function<double(double)> func,\n double a, double b) const\n{\n // do the integration\n}\n
  • Usage:\n
  • \n integrate(myfunc2,0,1); \n integrate([](double x){ return x; }, 0, 1);\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"b54d3253"},{"cell_type":"markdown","source":"

Program Design, continue

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"44c14e8f"},{"cell_type":"markdown","source":"

Base class Quadrature

","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d27c3b4b"},{"cell_type":"code","source":"class Quadrature\n{\n public:\n Quadrature()\n : n(0), weights(nullptr), points(nullptr) {};\n Quadrature(int n)\n : n(n), weights(new double[n]), points(new double[n]) {};\n ~Quadrature()\n { delete[] weights; delete[] points; n=0; }\n private:\n double* weights;\n double* points;\n int n;\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"af42c59d"},{"cell_type":"markdown","source":"

Base class Quadrature

\n
    \n
  • Scenario I: We want to declare the interface of the integrate function but we want to force the user\nto implement each integration rule individually\n
  • \n // pure (=0) virtual member function\nvirtual double integrate(double (*func)(double x), double a, double b) const = 0;\n

    // pure (=0) virtual member function\nvirtual double integrate(std::function<double(double)> func, double a, double b) const = 0;

    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"84de8363"},{"cell_type":"markdown","source":"

Base class Quadrature

\n
    \n
  • Keyword virtual ... = 0; declares the function to be pure virtual\n
  • \n
  • That is, each class that is derived from the abstract class\nQuadrature must(!!!) implement this function explicitly\n
  • \n
  • Otherwise, the compiler complains when the programmer forgets to implement a pure virtual function and tries to create an object of the derived but not fully implemented class\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"cdb082e1"},{"cell_type":"markdown","source":"

Abstract classes

\n
    A class with at least one pure virtual function is an abstract class and it is not possible to create an object thereof\n
\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"2699b8e1"},{"cell_type":"markdown","source":"

Base class Quadrature

\n
    \n
  • Scenario II: We provide a generic implementation but allow\nthe user to override it explicitly in a derived class\n
  • \n virtual double integrate(double (*func)(double x), double a, double b) const {...}\n

    virtual double integrate(std::function<double(double)> func, double a, double b) const{...}

    \n
  • Keyword virtual declares the function virtual. Virtual functions can be overridden in a derived class. If no overriding takes place, then the function implementation from the base class is used\n
  • \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"2fdba15b"},{"cell_type":"markdown","source":"

Base class Quadrature

","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"0ab3ebb0"},{"cell_type":"code","source":"class Quadrature {\n public:\n Quadrature()\n : n(0), weights(nullptr), points(nullptr) {};\n Quadrature(int n)\n : n(n), weights(new double[n]), points(new double[n]) {};\n ~Quadrature()\n { delete[] weights; delete[] points; n=0; }\n private:\n double* weights;\n double* points;\n int n;\n // pure virtual functions (implemented in derived class)\n virtual double mapping(double xi,\n double a, double b) const = 0;\n virtual double factor(double a, double b) const = 0;\n // virtual integration function (generic implementation)\n virtual double integrate(double (*func)(double x), double a, double b) const {\n double integral(0);\n for (auto i=0; i
Base class Quadrature
\n
    \n
  • The virtual integrate function makes use of the pure virtual functions factor and mapping
  • \n
  • Both functions are not implemented in class Quadrature\n
  • \n
  • It is therefore obvious that class Quadrature must be an abstract class (and cannot be instantiated) since some of its functions (here: integrate) are still unavailable\n
  • \n
  • Virtual functions make it is possible to call functions in the base class which will be implemented in the derived class\n
  • \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"418667c4"},{"cell_type":"markdown","source":"

Class MidpointRule

\n
  • Derive class MidpointRule from base class Quadrature\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5f8b3918"},{"cell_type":"code","source":"class MidpointRule : public Quadrature\n{\n // Implement pure virtual mapping function (not used!)\n virtual double mapping(double xi, double a, double b) const \n { return 0; }\n // Implement pure virtual factor function (not used!)\n virtual double factor(double a, double b) const\n { return 1; }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"a2c64486"},{"cell_type":"markdown","source":"

Class MidpointRule

\n
  • Derive class MidpointRule from base class Quadrature\n
  • \n class MidpointRule : public Quadrature\n{\n ...\n // Override the implementation of the virtual integrate \n // function from class Quadrature with own implementation \n virtual double integrate(double (*func)(double x),\n double a, double b) const\n {\n return (b-a)*func(0.5*(a+b));\n }\n};\n
\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d5ad2f2b"},{"cell_type":"markdown","source":"

Class SimpsonRule

\n
  • Derive class SimpsonRule from base class Quadrature\n
  • \n class SimpsonRule : public Quadrature\n{\n ...\n // Override implementation of virtual integrate function \n // function from class Quadrature with own implementation virtual double integrate(double (*func)(double x),\n double a, double b) const\n{\nreturn (b-a)/6.0*(func(a)+4.0*func(0.5*(a+b))+func(b));\n}\n};\n
\n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"074a5532"},{"cell_type":"markdown","source":"

Gauss Rule

\n
","metadata":{},"id":"d74d5d82-2bbf-463c-87ef-c025d826a344"},{"cell_type":"markdown","source":"

Program Design, revisited

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a6edea7c"},{"cell_type":"markdown","source":"

Program Design, revisited

\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"050778a8"},{"cell_type":"markdown","source":"

Class GaussRule, continue

\n
    \n
  • Attributes from base class are now visible in derived class\n
  • \n
  • Class GaussRule implements functions factor and mapping\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"07f62799"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const\n { return 0.5*(b-a); }\n virtual double mapping(double xi, double a, double b) const\n { return 0.5*(b-a)*xi+0.5*(a+b); }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"b0266ffd"},{"cell_type":"markdown","source":"

Class GaussRule, continue

\n
    \n
  • Class GaussRule implements the concrete factor and mapping\n
  • \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"e0b8dbbc"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n inline virtual double factor(double a, double b) const \n { return 0.5*(b-a); }\n \n inline virtual double mapping(double xi, double a, double b) const\n { return 0.5*(b-a)*xi+0.5*(a+b); }\n}","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"117aa302"},{"cell_type":"markdown","source":"
    \n
  • Inline specifier „suggests“ the compiler to substitute the function body instead of calling the function explicitly.\n
  • \n
","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"3d96720b"},{"cell_type":"markdown","source":"

Keyword: override (C++11)

\n
    \n
  • With the override keyword you can force the compiler to explicitly check that the function in a derived class\noverrides a (pure) virtual function from the base class\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"82c832b4"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const override\n { \n return 0.5 * (b - a); \n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"c30fee81"},{"cell_type":"markdown","source":"
    \n
  • If the base class Quadrature does not specify a (pure) virtual function factor an error will be thrown.
  • \n
","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"f28abb55"},{"cell_type":"markdown","source":"

Keyword: final (C++11)

\n
    \n
  • With the final keyword you can force the compiler to\nexplicitly prevent further overriding of functions
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"b90f4ef1"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const final\n { return 0.5*(b-a); }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"6b6f86a7"},{"cell_type":"markdown","source":"
    \n
  • If a class GaussRuleImproved derived from GaussRule tries to override the function factor an error will be thrown.
  • \n
","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"2320350a"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"92580323"}]} \ No newline at end of file +{"metadata":{"celltoolbar":"Slideshow","kernelspec":{"display_name":"C++17","language":"C++17","name":"xcpp17"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"code","source":"//-----------------------------Run this cell before the lecture-----------------------------\n#include \n#include ","metadata":{"tags":[]},"execution_count":null,"outputs":[],"id":"7a24648b"},{"cell_type":"markdown","source":"# Object-oriented scientific programming with C++\n\nMatthias Möller, Jonas Thies (Numerical Analysis, DIAM)\n\nLecture 2","metadata":{"slideshow":{"slide_type":"slide"}},"id":"876d5165"},{"cell_type":"markdown","source":"

Task: Dot product

\nWrite a $\\mathrm{C}++$ code that computes the dot product\n$$\na \\cdot b=\\sum_{i=1}^n a_i b_i\n$$\nof two vectors $a=\\left[a_1, a_2, \\ldots, a_n\\right]$ and $b=\\left[b_1, b_2, \\ldots, b_n\\right]$ and terminates if the two vectors have different length.","metadata":{"slideshow":{"slide_type":"slide"}},"id":"59398614"},{"cell_type":"markdown","source":"

Dot product function

\n\nThe main functionality without any fail-safe checks","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"00de1935"},{"cell_type":"code","source":"double dot_product(const double* a, int n, const double* b, int m)\n{\n double d=0.0;\n for (auto i=0; i
Dot product function - improved version
\n\nFirst version of the dot product with exception","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"6bfb7f00"},{"cell_type":"code","source":"#include \n#include \n\ndouble dot_product(const double* a, int n, const double* b, int m)\n{\n if (a == nullptr || b == nullptr) {\n // Handle null pointers by throwing an exception\n throw std::invalid_argument(\"Null pointer argument\");\n }\n\n if (n != m) {\n // Handle mismatched sizes by throwing an exception\n throw std::invalid_argument(\"Array sizes mismatch\");\n }\n\n // Core functionality\n double d = 0.0;\n for (int i = 0; i < n; ++i) {\n d += a[i] * b[i];\n }\n return d;\n}","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"77d96a85"},{"cell_type":"code","source":"double x[5] = { 1, 2, 3, 4, 5 };\ndouble y[4] = { 1, 2, 3, 4 };\ntry {\n double d = dot_product(x, 5, y, 4);\n} catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"tags":[]},"execution_count":null,"outputs":[],"id":"a56e3e96-c884-4b16-aeb9-fbf84fddc800"},{"cell_type":"markdown","source":"It would be much better if `x` and `y` would \"know\" their length internally so that the calling function cannot provide inconsistent data","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"b8e06e52"},{"cell_type":"markdown","source":"

Cool! But what was the reason I enrolled in this course?

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"ac26729d"},{"cell_type":"markdown","source":"

Flashback: Object Oriented Programming

\n

Imagine each LEGO block as a piece of data (like an integer, string, etc.). A struct or a class is like a LEGO set that contains a variety of different blocks.

\n
    \n
  • Main idea of OOP is to bundle data (e.g. array) and\nfunctionality (e.g. length) into a struct or class\n
  • \n
  • Components of a struct are public (=can be accessed from\noutside the struct) by default\n
  • \n
  • Components of a class are private (=cannot be accessed\nfrom outside the class) by default\n
  • \n
  • Components of a struct/class are attributes and member\nfunctions (=methods)\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5a0bb6d1"},{"cell_type":"markdown","source":"

Class vs. struct

\n
\nstruct Vector {\n public: //default\n double* array;\n int length;\n private:\n};\n
\n
\nclass Vector {\n private: //default\n public:\n double* array;\n int length;\n};\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"cb3fdc9a"},{"cell_type":"code","source":"class Vector {\n private: //default\n public:\n double* array;\n int length;\n};","metadata":{"tags":[],"slideshow":{"slide_type":"fragment"}},"execution_count":null,"outputs":[],"id":"35917d43-877b-46d7-972c-532cfb921af1"},{"cell_type":"markdown","source":"

When to use class and when to use struct?

\n
  • struct is typically used when you want to group data together without needing to restrict access to it. It is straightforward and simple. Many type traits (later in thios course) are implemented as structs.\n
  • \n
  • class is typically used when you want more control over the data and the interface through which it is accessed and manipulated, promoting the principles of encapsulation and data hiding.\n
  • \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"25f6d0fa"},{"cell_type":"markdown","source":"

Dot product as a member function of Vector

\n\nSecond version of the dot product using Vector class or struct","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5fb04b49"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector{\n public:\n double* array;\n int length;\n \n double dot_product(const Vector& a, const Vector& b)\n {\n if(a.length != b.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; istruct or class by dot-notation („.“)","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"9065c92c"},{"cell_type":"markdown","source":"Is the above implementation really OOP? How would you invoke it from the main program?","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"id":"21d0748b-421f-49c9-859f-eaa81085eec2"},{"cell_type":"code","source":"Vector x,y;\nx.array = new double[5]; x.length = 5;\ny.array = new double[5]; y.length = 5;\nfor (int i = 0; i < 5; ++i) {\n x.array[i] = i + 1; // 1, 2, 3, 4, 5\n y.array[i] = i + 1; // 1, 2, 3, 4, 5\n}\n \ntry {\n double result = /** ??? **/;\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"execution_count":null,"outputs":[],"id":"2756e20a-199a-40dd-b041-714151eb5308"},{"cell_type":"markdown","source":"The current implementation of the dot_product function comes from functional programming and is not OOP style because it takes two input arguments `x` and `y` and returns one output argument, the dot product. In other words, it is not attached to any object (`x` or `y`).","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"id":"3df90be7-9c6c-4cc3-bbf7-f68c6bd7bc65"},{"cell_type":"markdown","source":"If we want to implement a function inside a class or struct that is not attached to an object we have to define it as static","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"d23d2ba6-1233-4569-86cb-7472c77fbd74"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector{\n public:\n double* array;\n int length;\n \n double dot_product(const Vector& a, const Vector& b)\n {\n if(a.length != b.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; istatic member functions are invoked with the full classname (comparable to namespaces)","metadata":{},"id":"f5bbb4da-b866-4f1d-9578-f851f6215557"},{"cell_type":"code","source":"Vector x,y;\nx.array = new double[5]; x.length = 5;\ny.array = new double[5]; y.length = 5;\nfor (int i = 0; i < 5; ++i) {\n x.array[i] = i + 1; // 1, 2, 3, 4, 5\n y.array[i] = i + 1; // 1, 2, 3, 4, 5\n}\n \ntry {\n double result = Vector::dot_product(x, y);\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n}","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"767a8d52-d3fa-4ce2-90c5-5cab12bdf449"},{"cell_type":"markdown","source":"
    \n
  • It is still possible to initialise x.length by the wrong value, e.g.,\n
  • \n

    x.array = new double[5] {1, 2, 3, 4, 5}; x.length = 4;

    \n
  • The main function is not very readable due to the lengthy\ndeclaration, initialisation and deletion of data\n
  • \n
  • OOP solution:\n
  • \n
      \n
    • Constructor(s): method to construct a new Vector object\n
    • \n
    • Destructor: method to destruct an existing Vector object\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"412bd54a"},{"cell_type":"markdown","source":"

Constructor

\n\nThe constructor is called each time a new Vector object (=instance of the class Vector) is created","metadata":{"slideshow":{"slide_type":"slide"}},"id":"276ae022"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"7783126e"},{"cell_type":"markdown","source":"A class can have multiple constructors if they have a different interface (=different parameters)","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"ba2b6b0e"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n \n Vector(int len) // Another constructor\n {\n array = new double[len];\n length = len;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"0a655a26"},{"cell_type":"markdown","source":"What if a parameter has the same name as an attribute?","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"1ef93da1"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n Vector() // Default constructor\n {\n array = nullptr;\n length = 0;\n }\n \n Vector(int length) // Another constructor\n {\n // this pointer refers to the object itself,\n // hence this->length is the attribute and length\n // is the parameter passed to the constructor\n\n array = new double[length];\n this->length = length;\n }\n}; ","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"4034efd6"},{"cell_type":"markdown","source":"

Destructor

\n\nThe destructor is called implicitly at the end of the lifetime of a Vector object, e.g., at the end of its scope","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5708f6a6"},{"cell_type":"code","source":"class Vector\n{\n public:\n double* array;\n int length;\n \n ~Vector() // Destructor (and there can be only one!)\n {\n delete[] array;\n length = 0;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"ae4c1e5d"},{"cell_type":"markdown","source":"Cleaning up the main program with constructors and (implicitly invoked) destructors\n\nint main(){\n Vector x; // Default constructor is called\n {\n Vector y(5); // Constructor is called\n // Destructor is called for Vector y\n }\n // Destructor is called for Vector x\n }\n\nWithout array = nullptr in the default constructor the destruction of x will lead to a run-time error.","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"4133f84f"},{"cell_type":"markdown","source":"

Uniform initialisation constructors (C++11)

\n\nRemember this\ndouble x[5] = { 1, 2, 3, 4, 5 };\n\nIt would be cool to simply write\nVector x = { 1, 2, 3, 4, 5 };\n\n\nC++11 solution: initializer lists (#include <initializer_list>) and a special copy function (#include <memory>) \n\nVector(const std::initializer_list<double>& list) {\n length = (int)list.size();\n array = new double[length]; \n std::uninitialized_copy(list.begin(),\n list.end(), array);\n}","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"cf7475d7"},{"cell_type":"code","source":"class Vector {\nprivate:\n double* array;\n int length;\n\npublic:\n // Constructor with initializer list\n Vector(const std::initializer_list& list)\n {\n length = (int)list.size();\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n }\n \n // Destructor\n ~Vector() {\n delete[] array;\n }\n};","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"execution_count":null,"outputs":[],"id":"e9ecf2c0"},{"cell_type":"markdown","source":"

Dot product – close to perfection

\n\nThird version of the dot product using Vector class with uniform initialisation constructor (C++11) and exceptions\n\nint main()\n{\n Vector x = { 1, 2, 3, 4, 5 };\n Vector y = { 2, 4, 6, 8, 10};\n try {\n double dot_product(x, y);\n } catch (const std::exception &e) {\n std::cout << e.what() << std::endl;\n }\n}","metadata":{"slideshow":{"slide_type":"slide"}},"id":"afd5b6f8"},{"cell_type":"markdown","source":"

Delegating constructor (C++11)

\nCan we delegate some of the work, e.g., array = new double[length];","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"c0b1a899"},{"cell_type":"code","source":"class Vector {\nprivate:\n double* array;\n int length;\n\npublic:\n Vector(int length)\n {\n this->length = length;\n array = new double[length];\n }\n \n Vector(const std::initializer_list& list)\n {\n length = (int)list.size();\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n } \n} ","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"e900f59b"},{"cell_type":"markdown","source":"Delegating constructors delegate part of the work to\nanother constructor of the same or another class\n\nVector(int length) \n : length(length), array(new double[length])\n{ }\n\nHere, delegation is not really helpful but more a question\nof coding style, e.g., some programmers use delegation in\nall situation where this is technically possible\n\nIt is no longer necessary to distinguish between the\nattribute (this->length) and the argument (length) if both\nhave the same name. But be careful with the order in\nwhich delegated objects are constructed!","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"310e8ce7"},{"cell_type":"markdown","source":"

Quiz

\n
\n
    \n
  • Vector(int len)\n : length(len),\n array(new double[len])\n {}
  • \n
  • Vector(int len)\n : array(new double[len]),\n length(len)\n {}\n
  • \n
\n
\n
\n
    \n
  • Vector(int len)\n : length(len),\n array(new double[lengh])\n {}
  • \n
  • Vector(int len)\n : array(new double[lenth]),\n length(len)\n {}\n
  • \n
\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5378508f"},{"cell_type":"markdown","source":"If you have multiple constructors with increasing functionality, delegating constructors can be really helpful to remove duplicate code, e.g.\n\nVector(const std::initializer_list<double>& list)\n: Vector((int)list.size())\n{\n std::uninitialized_copy(list.begin(),\n list.end(), array);\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"0fd11e67"},{"cell_type":"markdown","source":"

Function -> member function

\n\nFunction that computes the sum of a Vector\n\nstatic double sum(const Vector& a)\n{\n double s = 0;\n for (auto i=0; i<a.length; i++)\n s += a.array[i];\n return s;\n}\n\nThis is not really OOP-style!\n\nint main() {\n Vector x = { 1, 2, 3, 4, 5 };\n std::cout << sum(x) << std::endl; \n}\n","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"58ab6c05"},{"cell_type":"markdown","source":"Implementation of sum as an OOP-style member function","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"b6a9be11"},{"cell_type":"code","source":"#include \n#include \n\nclass Vector {\n private:\n double* array;\n int length;\n \n public:\n Vector(const std::initializer_list& list) {\n length = static_cast(list.size());\n array = new double[length];\n std::uninitialized_copy(list.begin(), list.end(), array);\n }\n \n ~Vector() {\n delete[] array;\n }\n \n double sum() {\n double s = 0;\n for (int i = 0; i < length; i++) {\n s += array[i];\n }\n return s;\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"938ea41b"},{"cell_type":"markdown","source":" This is good OOP-style\n\nVector v = {1.0, 2.0, 3.0};\nstd::cout << v.sum() << std::endl;","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"fe1a3a38"},{"cell_type":"markdown","source":"Can we implement the dot_product function as a member function?","metadata":{"slideshow":{"slide_type":"slide"}},"id":"ccf5de20"},{"cell_type":"code","source":"class Vector {\n private:\n double* array;\n int length;\n \n public:\n double dot_product(const Vector& other) {\n if (length != other.length)\n throw std::invalid_argument(\"Vector lengths mismatch\");\n \n double d=0.0;\n for (auto i=0; iint main()\n{\n Vector x = {1,2,3}; Vector y = {2,4,6};\n std::cout << x.dot_product(y) << std::endl;\n std::cout << y.dot_product(x) << std::endl;\n}
\n\nFormally, the dot product is an operation between two\nVector objects and not a member function of one Vector\nobject that needs another Vector object for calculation","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"a16793a3"},{"cell_type":"markdown","source":"

Operator overloading

\nC++ allows to overload (=redefine) the standard operators\n\n- Unary operators: `++a`, `a++`, `--a`, `a--`, `~a`, `!a`\n- Binary operators: `a+b`, `a-b`, `a*b`, `a/b`\n- Relational operators: `a==b`, `a!=b`, `ab`, `a>=b`\n\nInterfaces:\n\n\nreturn_type operator() [const]\nreturn_type operator(const Vector& other) [const]

\n \n[Complete list:](https://en.cppreference.com/w/cpp/language/operators)https://en.cppreference.com/w/cpp/language/operators","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"c70addbd"},{"cell_type":"markdown","source":"Implementation of dot product as overloaded *-operator\n\ndouble operator*(const Vector& other) const\n {\n if (length != other.length)\n throw std::invalid_argument(\"Vector lengths mismatch\");\n double d=0.0;\n for (auto i=0; i<length; i++)\n d += array[i]*other.array[i];\n return d;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"257182ca"},{"cell_type":"markdown","source":"Now, the dot product is implemented as *-operator that\nmaps two Vector objects to a scalar value\n\nint main()\n{\n Vector x = {1,2,3}; Vector y = {2,4,6};\n std::cout << x * y << std::endl;\n std::cout << y * x << std::endl;\n}","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"d5960faa"},{"cell_type":"markdown","source":"The const specifier indicates that the Vector reference other must not be modified by the *-operator\n\nThe trailing const specifier indicates that the this pointer (aka, the object whose function is invoked) must not be modified by the *-operator\n\ndouble operator*(const Vector& other) const { ... }","metadata":{"tags":[],"slideshow":{"slide_type":"subslide"}},"id":"6719164c-c1f7-498a-822c-5bb7fb8e0d92"},{"cell_type":"markdown","source":"

Assignment by operator overloading

\n\nImplementation of assignment as overloaded =-operator\n\nVector& operator=(const Vector& other)\n{\n if (this != &other)\n {\n length = other.length;\n delete[] array;\n array = new double[length];\n for (auto i=0; i<length; ++i)\n array[i] = other.array[i];\n }\n return *this;\n}\n\n- Usage: Vector x, y; x = y;\n- Note that the this pointer is modified so there must not be a trailing const","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"37e996c0"},{"cell_type":"markdown","source":"Implementation of incremental assignment as overloaded =-operator\n\nVector& operator+=(const Vector& other)\n{\n if(length != other.length) \n throw std::invalid_argument(\"Vector lengths mismatch\");\n for (auto i=0; i<length; i++)\n array[i] += other.array[i];\n return *this;\n}\n\n- Usage: Vector x, y; x += y;\n- Note that the this pointer is modified so there must not be a trailing const","metadata":{"slideshow":{"slide_type":"subslide"},"tags":[]},"id":"9242b8b7-6046-4ad1-8f2f-16d8d737f44b"},{"cell_type":"markdown","source":"

Container class

","metadata":{"slideshow":{"slide_type":"skip"},"tags":[]},"id":"6de0c1bd"},{"cell_type":"code","source":"#include \n#include \n#include \n\nclass Container {\nprivate:\n int size;\n double* elements;\n\npublic:\n // Default constructor\n Container() : size(0), elements(nullptr) {\n std::cout << \"Default constructor called\" << std::endl;\n }\n\n // Converting constructor: can also initialize Container with an int\n Container(int length) : size(length), elements(new double[length]) {\n std::cout << \"Converting constructor (from int) called\" << std::endl;\n std::fill(elements, elements + size, 0.0);\n }\n\n // Converting constructor: can also initialize Container with an initializer_list\n Container(const std::initializer_list& list) \n : size(static_cast(list.size())), elements(new double[size]) {\n std::cout << \"Converting constructor (from initializer_list) called\" << std::endl;\n std::copy(list.begin(), list.end(), elements);\n }\n\n // Explicit constructor to prevent implicit conversions from a single float\n explicit Container(float initial_value) : size(1), elements(new double[1]) {\n std::cout << \"Explicit constructor called\" << std::endl;\n elements[0] = initial_value;\n }\n\n // Copy constructor\n Container(const Container& other) : size(other.size), elements(new double[other.size]) {\n std::cout << \"Copy constructor called\" << std::endl;\n std::copy(other.elements, other.elements + size, elements);\n }\n\n // Move constructor\n Container(Container&& other) noexcept : size(other.size), elements(other.elements) {\n std::cout << \"Move constructor called\" << std::endl;\n other.size = 0;\n other.elements = nullptr;\n }\n\n // Destructor\n ~Container() {\n std::cout << \"Destructor called\" << std::endl;\n delete[] elements;\n }\n\n // Copy assignment operator\n Container& operator=(const Container& other) {\n std::cout << \"Copy assignment operator called\" << std::endl;\n if (this != &other) { // Protect against self-assignment\n delete[] elements; // Free the existing resource.\n size = other.size;\n elements = new double[size];\n std::copy(other.elements, other.elements + size, elements);\n }\n return *this;\n }\n\n // Move assignment operator\n Container& operator=(Container&& other) noexcept {\n std::cout << \"Move assignment operator called\" << std::endl;\n if (this != &other) { // Prevent self-assignment\n delete[] elements; // Free the existing resource.\n size = other.size;\n elements = other.elements;\n other.size = 0;\n other.elements = nullptr;\n }\n return *this;\n }\n\n // A simple print function to show the contents\n void print() const {\n for (int i = 0; i < size; ++i) {\n std::cout << elements[i] << \" \";\n }\n std::cout << std::endl;\n }\n};\n\nint main() {\n Container defaultContainer; // Calls default constructor\n\n Container fromInt = 5; // Calls converting constructor with an int\n fromInt.print();\n\n Container fromList = {1.0, 2.0, 3.0}; // Calls converting constructor with an initializer_list\n fromList.print();\n\n // The line below will cause a compile-time error due to the 'explicit' keyword\n // Container fromFloat = 2.5f; // Error\n Container fromFloat(2.5f); // Calls explicit constructor\n fromFloat.print();\n\n Container copyContainer = fromList; // Calls copy constructor\n copyContainer.print();\n\n // Assigning a new value to copyContainer using copy assignment operator\n copyContainer = fromInt;\n copyContainer.print();\n\n Container moveContainer; // Calls default constructor\n moveContainer = std::move(copyContainer); // Calls move assignment operator\n moveContainer.print();\n\n return 0;\n}","metadata":{"tags":[],"slideshow":{"slide_type":"skip"}},"execution_count":null,"outputs":[],"id":"c7111714"},{"cell_type":"markdown","source":"

Container class

\nclass Container {\nprivate:\n double* data;\n int length;\npublic:\n Container(int length)\n : length(length), data(new double[length])\n { }\n Container(const std::initializer_list<double>& l)\n : Container( (int)l.size() )\n {\n std::uninitialized_copy(l.begin(), l.end(), data);\n }\n};","metadata":{"slideshow":{"slide_type":"slide"}},"id":"6bcfdf28"},{"cell_type":"markdown","source":"

Conversion constructors

\n\nBoth constructors convert a single input argument into a Container object, hence, they are called conversion constructors. They can be called in two different ways\n
    \n
  • Using the regular construction form
  • \nContainer a( 4 );\nContainer a( {1,2,3,4} );\n
  • Using copy initialisation
  • \nContainer a = 4; // -> Container a( 4 )\nContainer a = {1,2,3,4}; // -> Container a( {1,2,3,4} )\nContainer a = {4}; // which constructor is called?\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"41cb11de"},{"cell_type":"markdown","source":"

Explicit specifier

\n\nThe explicit specifier prevents the use of the constructor as conversion constructor\n\nexplicit Container(int length) \n : length(length), data(new double[length])\n { }\n}\n\nNow, copy-initialisation (Container a = 4;) is no longer possible\nbut explicit constructor (Container a( 4 );) has to be used","metadata":{"slideshow":{"slide_type":"slide"}},"id":"9da21506"},{"cell_type":"markdown","source":"

Constructors summary

\n\n\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n
ConstructorDescriptionUsage
DefaultConstructor with no parameters.Used to create an object with default values.
ParameterizedConstructor with parameters to initialize an object with specific values.Used to create an object with specified attributes.
CopyA constructor that initializes an object using another object of the same class.Used to create a copy of an object.
ExplicitConstructor with the explicit keyword to prevent implicit conversions or copy-initialization.Used to enforce explicit object creation with constructor.
\n\n","metadata":{"slideshow":{"slide_type":"slide"}},"id":"f41a401c"},{"cell_type":"markdown","source":"

Task: Numerical integration

\n
    \n
  • Approximate a one-dimensional integral by numerical quadrature
  • \n $$\\int_a^bf(x)dx \\approx \\sum_{i=1}^n \\omega_i f\\left(x_i\\right)$$\n
  • Choice of quadrature weights $w_i$ and points $x_i$ determines\n the concrete numerical integration rule
  • \n
\n ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a8d2812d"},{"cell_type":"markdown","source":"

Simple integration rules

\n
    \n
  • Midpoint rule\n
  • \n $$\\int_a^b f(x) d x \\approx(b-a) \\cdot f\\left(\\frac{a+b}{2}\\right)$$\n
  • Simpson rule\n
  • \n $$\\int_a^b f(x) d x \\approx \\frac{b-a}{6}\\left[f(a)+4 f\\left(\\frac{a+b}{2}\\right)+f(b)\\right]$$\n
  • Rectangle rule\n
  • \n $$\\int_a^b f(x) d x \\approx h \\sum_{n=0}^{N-1} f\\left(x_n\\right), \\quad h=\\frac{b-a}{N}, \\quad x_n=a+n h$$\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"84a5ecaf"},{"cell_type":"markdown","source":"

Gauss integration rules

\n
\n
    \n
  • Zoo of Gauss integration rules with quadrature weights and points tabulated for the reference interval [-1,1]\n
  • \n
  • Complete list of weights/points is available, e.g., at Wikipedia\n
  • \n
\n
\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
$n$$\\xi_{i}$$w_i$
102
2-0.577350269192
0.577350269191
3-0.7745966692415/9
0.08/9
0.7745966692415/9
4-0.8611363115940530.347854845137454
-0.3399810435848560.652145154862546
0.7745966692410.652145154862546
0.8611363115940530.347854845137454
\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"9bcf5ae9"},{"cell_type":"markdown","source":"
    \n
  • Change of variable theorem\n
  • \n $$\\int_{a}^{b} f(x)dx = \\int_{-1}^{1}f(\\phi(t))\\phi^{i}(t)dt$$\n
  • Mapping from interval [a,b] to interval [-1,1]\n
  • \n $$\\phi(t) = \\frac{b-a}{2}t + \\frac{a+b}{2}, \\phi^{'}(t) = \\frac{b-a}{2}$$\n
  • Numerical quadrature rule\n
  • \n $$\\int_{a}^{b}f(x)dx\\approx\\phi^{'}\\sum_{n=1}^{n}w_if(\\phi(\\xi_i))$$\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"c1e5486d"},{"cell_type":"markdown","source":"

Program design

\n\nWe need...\n
    \n
  • A strategy to ensure that all numerical quadrature rules\n(=classes) provide an identical interface for evaluating integrals\n
  • \n
  • A standard way to pass user-definable function f(x) from outside (=main routine) to the evaluation function\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a5f156a5"},{"cell_type":"markdown","source":"
    \n
  • A strategy to ensure that all numerical quadrature rules (=classes) provide an identical interface for evaluating integrals\n
  • \n
      \n
    • Polymorphism: Base class Quadrature provides common attributes and member functions (at least their interface declaration); derived classes implement specific quadrature rule (reusing common functionality of the base class, where this is possible and makes sense)\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"e3ca4745"},{"cell_type":"markdown","source":"
    \n
  • A standard way to pass user-definable function f(x) from outside (=main routine) to the evaluation function\n
  • \n
      \n
    • Function pointers (traditional approach)\n
    • \n
    • Lambda expressions (recommended approach since C++11)\n
    • \n
    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"7adb6a72"},{"cell_type":"markdown","source":"

Function pointers

\n
    \n
  • Define a function to be integrated\n
  • \n const double myfunc1(double x){ return x; }\n
  • Define interface of the integrate function\n
  • \n double integrate(const double (*func)(double x), double a, double b) { ... }\n
  • Usage:integrate(myfunc1, 0, 1);\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"be00d9e9"},{"cell_type":"markdown","source":"

Lambda expressions

\n
    \n
  • Introduced in C++11, lambda expressions provide an elegant way to write user-defined callback functions\n
  • \n
  • General syntax\n
  • \n auto name = [<captures>] (<parameters>) {<body>};\n
  • Lambda expressions can be inlined (anonymous functions)\n
  • \n integrate([<captures>](<parameters>) {<body>});\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"97851d03"},{"cell_type":"markdown","source":"
    \n
  • Define function to be integrated\n
  • \n auto myfunc2 = [](double x) { return x; };\n
  • Define interface of the integration function\n
  • \n double integrate(std::function<double(double)> func, double a, double b) const { ... }\n
  • Usage:\n
  • \n integrate(myfunc2, 0, 1); \n integrate([](double x){ return x; }, 0, 1);\n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"b54d3253"},{"cell_type":"markdown","source":"

Program design, revisited

\n
","metadata":{"slideshow":{"slide_type":"slide"}},"id":"44c14e8f"},{"cell_type":"markdown","source":"

Base class Quadrature

","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"d27c3b4b"},{"cell_type":"code","source":"class Quadrature\n{\n public:\n Quadrature()\n : n(0), weights(nullptr), points(nullptr) {};\n Quadrature(int n)\n : n(n), weights(new double[n]), points(new double[n]) {};\n ~Quadrature()\n { delete[] weights; delete[] points; n=0; }\n private:\n double* weights;\n double* points;\n int n;\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"af42c59d"},{"cell_type":"markdown","source":"
  • Scenario I: We want to declare the interface of the integrate function but we want to force the user to implement each integration rule individually
  • \n\n// pure (=0) virtual member function\nvirtual double integrate(double (*func)(double x), double a, double b) const = 0;\n

    // pure (=0) virtual member function\nvirtual double integrate(std::function<double(double)> func, double a, double b) const = 0;

    \n
","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"84de8363"},{"cell_type":"markdown","source":"
    \n
  • Keyword virtual ... = 0; declares the function to be pure virtual\n
  • \n
  • That is, each class that is derived from the abstract class\nQuadrature must(!!!) implement this function explicitly\n
  • \n
  • Otherwise, the compiler complains when the programmer forgets to implement a pure virtual function and tries to create an object of the derived but not fully implemented class\n
  • \n
","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"id":"cdb082e1"},{"cell_type":"markdown","source":"

Abstract classes

\n
    A class with at least one pure virtual function is an abstract class and it is not possible to create an object thereof\n
\n
","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"2699b8e1"},{"cell_type":"markdown","source":"

Base class Quadrature

\n
    \n
  • Scenario II: We provide a generic implementation but allow\nthe user to override it explicitly in a derived class\n
  • \n virtual double integrate(double (*func)(double x), double a, double b) const {...}\n

    virtual double integrate(std::function<double(double)> func, double a, double b) const{...}

    \n
  • Keyword virtual declares the function virtual. Virtual functions can be overridden in a derived class. If no overriding takes place, then the function implementation from the base class is used\n
  • \n
","metadata":{"slideshow":{"slide_type":"slide"},"tags":[]},"id":"2fdba15b"},{"cell_type":"code","source":"class Quadrature {\n public:\n Quadrature()\n : n(0), weights(nullptr), points(nullptr) {};\n Quadrature(int n)\n : n(n), weights(new double[n]), points(new double[n]) {};\n ~Quadrature()\n { delete[] weights; delete[] points; n=0; }\n \n private:\n double* weights;\n double* points;\n int n;\n \n // ** pure virtual functions (implemented in derived class) ** /\n virtual double mapping(double xi, double a, double b) const = 0;\n virtual double factor(double a, double b) const = 0;\n \n // ** virtual integration function (generic implementation) ** /\n virtual double integrate(double (*func)(double x), double a, double b) const {\n double integral(0.0);\n for (auto i=0; i\n
  • The virtual integrate function makes use of the pure virtual functions factor and mapping
  • \n
  • Both functions are not implemented in class Quadrature\n
  • \n
  • It is therefore obvious that class Quadrature must be an abstract class (and cannot be instantiated) since some of its functions (here: integrate) are still unavailable\n
  • \n
  • Virtual functions make it is possible to call functions in the base class which will be implemented in the derived class\n
  • \n","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"418667c4"},{"cell_type":"markdown","source":"

    Class MidpointRule

    \n
    • Derive class MidpointRule from base class Quadrature\n
    • \n
    ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"5f8b3918"},{"cell_type":"code","source":"class MidpointRule : public Quadrature\n{\n // Implement pure virtual functions (not used but need to be implemented!)\n virtual double mapping(double xi, double a, double b) const { return 0; }\n virtual double factor(double a, double b) const { return 1; }\n \n // Override the implementation of the virtual integrate \n // function from class Quadrature with own implementation \n virtual double integrate(double (*func)(double x), double a, double b) const\n {\n return (b-a) * func( 0.5 * (a+b) );\n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"a2c64486"},{"cell_type":"markdown","source":"

    Class SimpsonRule

    \n
    • Derive class SimpsonRule from base class Quadrature\n
    • \n
    ","metadata":{},"id":"4fe75525-c7ff-417d-94b1-3fee62185f9b"},{"cell_type":"code","source":"class SimpsonRule : public Quadrature\n{\n // Implement pure virtual functions (not used but need to be implemented!)\n virtual double mapping(double xi, double a, double b) const { return 0; }\n virtual double factor(double a, double b) const { return 1; }\n \n // Override the implementation of the virtual integrate \n // function from class Quadrature with own implementation \n virtual double integrate(double (*func)(double x), double a, double b) const\n {\n return (b-a)/6.0 * ( func(a) + 4.0 * func( 0.5*(a+b) ) + func(b) );\n }\n};","metadata":{},"execution_count":null,"outputs":[],"id":"c72efd90-969a-4bd4-a495-499fe88d43bd"},{"cell_type":"markdown","source":"

    Class GaussRule

    \n
    ","metadata":{},"id":"d74d5d82-2bbf-463c-87ef-c025d826a344"},{"cell_type":"markdown","source":"

    Program design, revisited

    \n
    ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"a6edea7c"},{"cell_type":"markdown","source":"

    Program design, revisited

    \n
    ","metadata":{"slideshow":{"slide_type":"subslide"}},"id":"050778a8"},{"cell_type":"markdown","source":"

    Class GaussRule

    \n
      \n
    • Attributes from base class are now visible in derived class\n
    • \n
    • Class GaussRule implements functions factor and mapping\n
    • \n
    ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"07f62799"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const \n { return 0.5*(b-a); }\n \n virtual double mapping(double xi, double a, double b) const \n { return 0.5*(b-a)*xi+0.5*(a+b); }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"b0266ffd"},{"cell_type":"markdown","source":"

    Keyword: override (C++11)

    \n
      \n
    • With the override keyword you can force the compiler to explicitly check that the function in a derived class\noverrides a (pure) virtual function from the base class\n
    • \n
    ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"82c832b4"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const override\n { \n return 0.5 * (b - a); \n }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"c30fee81"},{"cell_type":"markdown","source":"
      \n
    • If the base class Quadrature does not specify a (pure) virtual function factor an error will be thrown.
    • \n
    ","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"f28abb55"},{"cell_type":"markdown","source":"

    Keyword: final (C++11)

    \n
      \n
    • With the final keyword you can force the compiler to\nexplicitly prevent further overriding of functions
    • \n
    ","metadata":{"slideshow":{"slide_type":"slide"}},"id":"b90f4ef1"},{"cell_type":"code","source":"class GaussRule : public Quadrature\n{\n virtual double factor(double a, double b) const final\n { return 0.5*(b-a); }\n};","metadata":{"slideshow":{"slide_type":"fragment"},"tags":[]},"execution_count":null,"outputs":[],"id":"6b6f86a7"},{"cell_type":"markdown","source":"
      \n
    • If a class GaussRuleImproved derived from GaussRule tries to override the function factor an error will be thrown.
    • \n
    ","metadata":{"slideshow":{"slide_type":"fragment"}},"id":"2320350a"}]} \ No newline at end of file