Skip to content
Snippets Groups Projects
Commit d3dda031 authored by Marcus Mohr's avatar Marcus Mohr
Browse files

Adds sections on override, final and pure virtual functions

parent cfcc2a3b
Branches
No related merge requests found
%% 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 <a href="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.
%% Cell type:code id:bd457522 tags:
``` C++14
#include <iostream>
struct A {
void func1() { std::cout << "A::func1 is executing" << std::endl; };
virtual void func2( double a ) { std::cout << "func2 from A: a = " << a << std::endl; };
};
struct B : public A {
void func1() { std::cout << "B::func1 is executing" << std::endl; };
virtual void func2( int a ) { std::cout << "func2 from B: a = " << a << std::endl; };
};
```
%% Cell type:code id:4a0a85ba tags:
``` C++14
A* obj = new B;
obj->func1();
obj->func2( 2 );
```
%% Output
A::func1 is executing
func2 from A: a = 2
%% Cell type:markdown id:6414fc0e tags:
`A::func1` is not virtual, so we don't overide and while `A::func2` is virtual, the interface of `B::func2` is different.
A smart compiler might warn us on the latter:
%% Cell type:code id:3c824171 tags:
``` C++14
%%file fail.cpp
#include <iostream>
struct A {
void func1() { std::cout << "A::func1 is executing" << std::endl; };
virtual void func2( double a ) { std::cout << "func2 from A: a = " << a
<< std::endl; };
};
struct B : public A {
void func1() { std::cout << "B::func1 is executing" << std::endl; };
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**.
%% Cell type:code id:3069e8e2 tags:
``` C++14
class Vector;
class BaseMatrix {
virtual void multWithVector( const Vector& src, Vector& dst ) const = 0;
}
```
%% Cell type:markdown id:ec47a18b tags:
* A pure virtual function has no implementation in the class where it is declared. It only describes an **interface**.
* Adding a pure virtual function to a class makes it an **abstract class**, i.e. we cannot instantiate an object of that class.
* The same holds for its children (via inheritance), as long as they do not implement the method.
%% Cell type:code id:24c9d364 tags:
``` C++14
```
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment