GitLab now enforces expiry dates on tokens that originally had no set expiration date. Those tokens were given an expiration date of one year later. Please review your personal access tokens, project access tokens, and group access tokens to ensure you are aware of upcoming expirations. Administrators of GitLab can find more information on how to identify and mitigate interruption in our documentation.
"We return to our example from [Go19] and extend it a little."
"We return to our example from <a href=\"https://www.hanser-kundencenter.de/fachbuch/artikel/9783446458468\">[Go19]</a> and extend it a little."
]
},
{
...
...
@@ -41,7 +41,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 2,
"id": "f40024e2",
"metadata": {},
"outputs": [],
...
...
@@ -64,7 +64,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"id": "459a1ce0",
"metadata": {},
"outputs": [],
...
...
@@ -95,7 +95,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 4,
"id": "5ff4684f",
"metadata": {},
"outputs": [],
...
...
@@ -127,7 +127,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 5,
"id": "aebeaf35",
"metadata": {},
"outputs": [
...
...
@@ -156,7 +156,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 6,
"id": "445c4d28",
"metadata": {},
"outputs": [
...
...
@@ -187,7 +187,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 7,
"id": "4b596bd2",
"metadata": {},
"outputs": [],
...
...
@@ -240,7 +240,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 8,
"id": "12bebbf9",
"metadata": {},
"outputs": [
...
...
@@ -302,7 +302,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 9,
"id": "ccb162c8",
"metadata": {
"scrolled": true
...
...
@@ -378,7 +378,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 10,
"id": "99352379",
"metadata": {},
"outputs": [],
...
...
@@ -407,10 +407,193 @@
"secretary->all_info();"
]
},
{
"cell_type": "markdown",
"id": "80e7e3b3",
"metadata": {},
"source": [
"Especially in larger code bases it can be come tedious to not loose the overview on which member functions are virtual and which are not. Or whether we are overriding the correct version of a method."
" virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a\n",
" << std::endl; };\n",
"};"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "29c729c5",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"fail.cpp:14:16: warning: 'B::func2' hides overloaded virtual function [-Woverloaded-virtual]\n",
" virtual void func2( int a ) { std::cout << \"func2 from B: a = \" << a\n",
" ^\n",
"fail.cpp:7:16: note: hidden overloaded virtual function 'A::func2' declared here: type mismatch at 1st parameter ('double' vs 'int')\n",
" virtual void func2( double a ) { std::cout << \"func2 from A: a = \" << a\n",
" ^\n",
"1 warning generated.\n"
]
}
],
"source": [
"!clang++ -Wall -Wextra -c fail.cpp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6774e9d0",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "413b3e71",
"metadata": {},
"source": [
"### Pure Virtual Functions and Abstract Classes"
]
},
{
"cell_type": "markdown",
"id": "ebb14ead",
"metadata": {},
"source": [
"Consider the following application scenario:\n",
"\n",
"- We want to implement four specialised matrix classes\n",
" - `DenseMatrix`\n",
" - `DenseSymMatrix`\n",
" - `SparseMatrix`\n",
" - `SparseSymMatrix`\n",
"- They all should be children of a common `BaseMatrix` base class.\n",
"- All of them should provide a method `multWithVector()` with the same signature.\n",
"- We want to be able to invoke that method on a base class pointer or reference.\n",
"\n",
" This gives rise to the following questions:\n",
" 1. How should `BaseMatrix::multWithVector()` be implemented?\n",
" 1. Can we enforce that someone who adds another child, say `DenseSkewMatrix` does not forget to implement `DenseSkewMatrix::multWithVector()`?\n",
"\n",
"A sensible implementation of `BaseMatrix::multWithVector()` will not be possible, as the base class has no knowledge on the details of how its children store their matrices. We could only do an empty implementation or one that e.g. throws an exception. The latter would at least lead to a runtime crash, if we call `multWithVecotr()` on a base class pointer or reference that targets a `DenseSkewMatrix`.\n",
"\n",
"The preferred solution, however, would be to make `BaseMatrix::multWithVector()` a **pure virtual function**."
"* A pure virtual function has no implementation in the class where it is declared. It only describes an **interface**.\n",
"* Adding a pure virtual function to a class makes it an **abstract class**, i.e. we cannot instantiate an object of that class.\n",
"* The same holds for its children (via inheritance), as long as they do not implement the method."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f63ea43",
"id": "24c9d364",
"metadata": {},
"outputs": [],
"source": []
...
...
%% Cell type:markdown id:1a6233be tags:
# Polymorphic or Virtual Classes
%% Cell type:markdown id:7ec80016 tags:
We return to our example from [Go19] and extend it a little.
We return to our example from <ahref="https://www.hanser-kundencenter.de/fachbuch/artikel/9783446458468">[Go19]</a> and extend it a little.
%% Cell type:code id:7942848b tags:
``` C++14
#include <iostream>
class person {
public:
person( const std::string& name ) : name(name) {};
void all_info() const {
std::cout << "[person] My name is " << name << std::endl;
}
protected:
std::string name;
};
```
%% Cell type:code id:f40024e2 tags:
``` C++14
class student : public person {
public:
student( const std::string& name, const std::string program ) :
person(name), program(program) {}
void all_info() const {
std::cout << "[student] My name is " << name
<< ", I study " << program << std::endl;
}
private:
std::string program;
};
```
%% Cell type:code id:459a1ce0 tags:
``` C++14
class researcher : public person {
public:
researcher( const std::string& name, const std::string& field ) :
person(name), field(field) {}
void all_info() const {
std::cout << "[researcher] My name is " << name
<< ", I work on problems in " << field << std::endl;
}
private:
std::string field;
};
```
%% Cell type:markdown id:c5c9fb7a tags:
As `student`s and `researcher`s are `person`s we can e.g. generated a vector of person pointers, where each pointer targets a different university member.
%% Cell type:code id:5ff4684f tags:
``` C++14
#include <vector>
student joe( "Joe", "Geophysics" );
student jane( "Jane", "Geology" );
researcher erika( "Erika", "Seismology" );
person franz( "Franz" );
std::vector<person*> uniMembers;
uniMembers.push_back( &joe );
uniMembers.push_back( &jane );
uniMembers.push_back( &erika );
uniMembers.push_back( &franz );
```
%% Cell type:markdown id:56cad921 tags:
* Because of inheritance, we can now have all pointers to our different people in the same vector, although the targeted objects are of different type.
* Problem is, when we work with the vectors and ask for info we only get the minimal stuff from the base class
%% Cell type:code id:aebeaf35 tags:
``` C++14
for( auto memb: uniMembers ) memb->all_info();
```
%% Output
[person] My name is Joe
[person] My name is Jane
[person] My name is Erika
[person] My name is Franz
%% Cell type:markdown id:3f7ae736 tags:
Instead of the full records, such as
%% Cell type:code id:445c4d28 tags:
``` C++14
jane.all_info();
erika.all_info();
```
%% Output
[student] My name is Jane, I study Geology
[researcher] My name is Erika, I work on problems in Seismology
%% Cell type:markdown id:977efc72 tags:
The solution to this is to make our classes **polymorphic**.
*Definition:*
A class that contains at least one **virtual function** is called **polymorphic** or **virtual**.
%% Cell type:code id:4b596bd2 tags:
``` C++14
#include <iostream>
class person {
public:
person( const std::string& name ) : name(name) {};
virtual void all_info() const {
std::cout << "[person] My name is " << name << std::endl;
}
protected:
std::string name;
};
class student : public person {
public:
student( const std::string& name, const std::string program ) :
person(name), program(program) {}
void all_info() const {
std::cout << "[student] My name is " << name
<< ", I study " << program << std::endl;
}
private:
std::string program;
};
class researcher : public person {
public:
researcher( const std::string& name, const std::string& field ) :
person(name), field(field) {}
void all_info() const {
std::cout << "[researcher] My name is " << name
<< ", I work on problems in " << field << std::endl;
}
private:
std::string field;
};
```
%% Cell type:code id:12bebbf9 tags:
``` C++14
#include <vector>
student joe( "Joe", "Geophysics" );
student jane( "Jane", "Geology" );
researcher erika( "Erika", "Seismology" );
person franz( "Franz" );
std::vector<person*> uniMembers;
uniMembers.push_back( &joe );
uniMembers.push_back( &jane );
uniMembers.push_back( &erika );
uniMembers.push_back( &franz );
for( auto memb: uniMembers ) memb->all_info();
```
%% Output
[student] My name is Joe, I study Geophysics
[student] My name is Jane, I study Geology
[researcher] My name is Erika, I work on problems in Seismology
[person] My name is Franz
%% Cell type:markdown id:ef04a619 tags:
**Explanation**
So what is the difference here? Whenever we work with a **pointer**`pVar` or a **reference**`rVar` to a class and invoke a function `myFunc()` via that on the target the compiler will check the following:
1. What is the **static type** of `pVal` and or `pVar`, i.e. how were they declared?
1. Does that class have a method `myFunc()`?
1. Is `myFunc()` accessible, i.e. public, or not, i.e. private?
1. Is it a **virtual function**?
* No: Then invoke it.
* Yes: Check what the **dynamic type** is of `pVal` and or `pVar`, i.e. the type of the object they target.
Then invoke `myFunc()` for that type.
%% Cell type:markdown id:ae46b4e3 tags:
**Remark #1:**
This only works for pointers and references, but not for variables, even if they are initialised or copy constructed from a child object.
%% Cell type:code id:ccb162c8 tags:
``` C++14
person alias{joe};
alias.all_info();
```
%% Output
[person] My name is Joe
%% Cell type:markdown id:2d6da352 tags:
Only the parts of `joe` that belong to its `person` base class are used for the initialisation. This is sometimes refered to as **slicing**.
%% Cell type:markdown id:ad22ac97 tags:
**Remark #2:**
The decision which version of `all_info()` must be invoked **cannot** be made by the compiler, as it may not have the necessary information on the type of the target of a pointer or reference. It can only be made at **runtime**.
%% Cell type:markdown id:bcb61161 tags:
#### Terminology
Since the decision which virtual method is invoked on a pointer or reference to a polymorphic object is performed at runtime, one calls this **late** or **dynamic binding**.
It constitutes a form of **dynamic polymorphism**, as opposed to the static polymorphism of overloading or templates, which get's resolved at compile-time.
%% Cell type:markdown id:370a4417 tags:
#### Costs
In order to be able to correctly dispatch the virtual function call at runtime, the compiler generates a **virtual function table** (also **virtual method table**, **vtable**, **dispatch table**). The necessary table lookup constitutes an indirection and increases the costs for the function invokation.
If your function is short (i.e. does not perform a lot of work) then this extra overhead can be critically significant. If you function is long is will be negligible.
%% Cell type:markdown id:e27f36b9 tags:
### Overriding
%% Cell type:markdown id:960b3f74 tags:
In our example all child classes (`student` and `researcher`) have implemented their own version of `all_info()` and, thus, **overridden** the one from the base class. This is, however, not required. If the base class method is not overridden in a child, the base class method will simply be used.
%% Cell type:code id:99352379 tags:
``` C++14
class staff : public person{
using person::person;
};
```
%% Cell type:code id:454ccaaf tags:
``` C++14
person* secretary = new staff( "Linda" );
secretary->all_info();
```
%% Output
[person] My name is Linda
%% Cell type:code id:9f63ea43 tags:
%% Cell type:markdown id:80e7e3b3 tags:
Especially in larger code bases it can be come tedious to not loose the overview on which member functions are virtual and which are not. Or whether we are overriding the correct version of a method.
virtual void func2( int a ) { std::cout << "func2 from B: a = " << a
<< std::endl; };
};
```
%% Output
Writing fail.cpp
%% Cell type:code id:29c729c5 tags:
``` C++14
!clang++ -Wall -Wextra -c fail.cpp
```
%% Output
fail.cpp:14:16: warning: 'B::func2' hides overloaded virtual function [-Woverloaded-virtual]
virtual void func2( int a ) { std::cout << "func2 from B: a = " << a
^
fail.cpp:7:16: note: hidden overloaded virtual function 'A::func2' declared here: type mismatch at 1st parameter ('double' vs 'int')
virtual void func2( double a ) { std::cout << "func2 from A: a = " << a
^
1 warning generated.
%% Cell type:code id:6774e9d0 tags:
``` C++14
```
%% Cell type:markdown id:413b3e71 tags:
### Pure Virtual Functions and Abstract Classes
%% Cell type:markdown id:ebb14ead tags:
Consider the following application scenario:
- We want to implement four specialised matrix classes
- `DenseMatrix`
- `DenseSymMatrix`
- `SparseMatrix`
- `SparseSymMatrix`
- They all should be children of a common `BaseMatrix` base class.
- All of them should provide a method `multWithVector()` with the same signature.
- We want to be able to invoke that method on a base class pointer or reference.
This gives rise to the following questions:
1. How should `BaseMatrix::multWithVector()` be implemented?
1. Can we enforce that someone who adds another child, say `DenseSkewMatrix` does not forget to implement `DenseSkewMatrix::multWithVector()`?
A sensible implementation of `BaseMatrix::multWithVector()` will not be possible, as the base class has no knowledge on the details of how its children store their matrices. We could only do an empty implementation or one that e.g. throws an exception. The latter would at least lead to a runtime crash, if we call `multWithVecotr()` on a base class pointer or reference that targets a `DenseSkewMatrix`.
The preferred solution, however, would be to make `BaseMatrix::multWithVector()` a **pure virtual function**.