Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 8ab482f4 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Report some typos from docker run -p 8888:8888 -it -e DISPLAY=:0 -v...

Report some typos from docker run -p 8888:8888 -it -e DISPLAY=:0 -v /tmp/.X11-unix:/tmp/.X11-unix -v /Users/sebastien/Documents/Formations/GettingStartedCpp:/home/non_root_user/Formation/Cpp  --cap-drop=all formation_cpp:latest (they were 'lost' follwoing the splitting of one notebook in half).
parent 3f7a3230
Branches
No related tags found
No related merge requests found
Pipeline #247564 passed
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-inheritance" data-toc-modified-id="Introduction-to-inheritance-1">Introduction to inheritance</a></span><ul class="toc-item"><li><span><a href="#Multiple-layer-of-inheritance" data-toc-modified-id="Multiple-layer-of-inheritance-1.1">Multiple layer of inheritance</a></span></li><li><span><a href="#Multiple-inheritance" data-toc-modified-id="Multiple-inheritance-1.2">Multiple inheritance</a></span></li><li><span><a href="#Diamond-inheritance" data-toc-modified-id="Diamond-inheritance-1.3">Diamond inheritance</a></span></li></ul></li><li><span><a href="#Public-inheritance,-private-inheritance-and-composition" data-toc-modified-id="Public-inheritance,-private-inheritance-and-composition-2">Public inheritance, private inheritance and composition</a></span><ul class="toc-item"><li><span><a href="#IS-A-relationship-of-public-inheritance" data-toc-modified-id="IS-A-relationship-of-public-inheritance-2.1">IS-A relationship of public inheritance</a></span></li><li><span><a href="#IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-private-inheritance" data-toc-modified-id="IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-private-inheritance-2.2">IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance</a></span></li><li><span><a href="#CONTAINS-A-relationship-of-composition" data-toc-modified-id="CONTAINS-A-relationship-of-composition-2.3">CONTAINS-A relationship of composition</a></span></li><li><span><a href="#Private-inheritance-vs-composition" data-toc-modified-id="Private-inheritance-vs-composition-2.4">Private inheritance vs composition</a></span></li></ul></li><li><span><a href="#Protected-status" data-toc-modified-id="Protected-status-3">Protected status</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction to inheritance
Sometimes, you might want to define two types that are related: one might be an extension of the other, or they may share some similarities we would like to put in common.
Let's suppose for instance you are asked to register all vehicles owned by your company. You could define independent classes `Bicycle`, `Scooter` and `Car`, but as a result storing them in a same array (or even better `std::vector`) would be impossible.
The idea of inheritance is to provide a **base class** from which our classes are derived:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
enum class motor_type { none, thermic, electric };
class Vehicle
{
public:
Vehicle(motor_type type);
void Print() const { std::cout << "I'm a Vehicle!" << std::endl; }
private:
motor_type type_;
};
```
%% Cell type:code id: tags:
``` C++17
Vehicle::Vehicle(motor_type type)
: type_(type)
{ }
```
%% Cell type:code id: tags:
``` C++17
class ElectricBicycle : public Vehicle
{
public:
ElectricBicycle();
};
```
%% Cell type:code id: tags:
``` C++17
class ThermicCar : public Vehicle
{
public:
ThermicCar();
};
```
%% Cell type:code id: tags:
``` C++17
ElectricBicycle::ElectricBicycle()
: Vehicle(motor_type::electric)
{ }
```
%% Cell type:code id: tags:
``` C++17
ThermicCar::ThermicCar()
: Vehicle(motor_type::thermic)
{ }
```
%% Cell type:markdown id: tags:
A bit of syntax first:
* See the structure in declaring the derived classes: there is a `:` followed by the keyword `public` and the name of the class.
* The derived constructors must first call one of the base class constructor. If none specified, the default one without arguments is called *if existing*...
The base class part is constructed first, and then the elements specific to the derived class are added (that will be important - we will come back to that).
Likewise, destruction is performed in reverse order: first the specific part of the derived class, then the base class (in most cases you don't have to care about that).
### Multiple layer of inheritance
A child class may also be the parent of another class (unless `final` is used - see later)
A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)).
%% Cell type:code id: tags:
``` C++17
class ElectricVehicle : public Vehicle
{
public:
ElectricVehicle();
};
```
%% Cell type:code id: tags:
``` C++17
ElectricVehicle::ElectricVehicle()
: Vehicle(motor_type::electric)
{ }
```
%% Cell type:code id: tags:
``` C++17
class ElectricCar : public ElectricVehicle
{
public:
ElectricCar() = default;
};
```
%% Cell type:markdown id: tags:
### Multiple inheritance
It is also possible for a class to inherit from several parents:
%% Cell type:code id: tags:
``` C++17
class BlueObjects
{
public:
BlueObjects() = default;
int Print()
{
std::cout << "I'm blue!" << std::endl;
return 42;
}
}
```
%% Cell type:code id: tags:
``` C++17
class BlueVehicle : public Vehicle, public BlueObjects
{
public:
BlueVehicle(motor_type type);
};
```
%% Cell type:code id: tags:
``` C++17
BlueVehicle::BlueVehicle(motor_type type)
: Vehicle(type), // mandatory call of the non-default constructor
BlueObjects() // not mandatory: default constructor called
{ }
```
%% Cell type:code id: tags:
``` C++17
BlueVehicle blue_bike(motor_type::none);
```
%% Cell type:markdown id: tags:
**Beware:** there might be ambiguity between some methods names, regardless of prototype:
%% Cell type:code id: tags:
``` C++17
blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ?
```
%% Cell type:markdown id: tags:
It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place:
%% Cell type:code id: tags:
``` C++17
blue_bike.BlueObjects::Print();
```
%% Cell type:code id: tags:
``` C++17
blue_bike.Vehicle::Print();
```
%% Cell type:markdown id: tags:
### Diamond inheritance
In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`:
%% Cell type:markdown id: tags:
<img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/>
%% Cell type:markdown id: tags:
I really advise you to avoid this kind of things whenever possible, but if you _really_ need this:
- First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation?
- Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line).
%% Cell type:markdown id: tags:
## Public inheritance, private inheritance and composition
### IS-A relationship of public inheritance
So far, we have derived **publicly** the base class (hence the **public** keyword in the inheritance declaration), and this defines a **IS-A** relationship: the derived object is expected to be acceptable *everywhere* a base object is deemed acceptable.
This might seem trivial, but is not as obvious at it seems. Let's consider a counter-intuitive example:
%% Cell type:code id: tags:
``` C++17
#include <string>
class Rectangle
{
public:
Rectangle(double width, double length);
void SetWidth(double x);
void SetLength(double x);
double GetWidth() const;
double GetLength() const;
void Print() const;
private:
double width_ = -1.e20; // Stupid value that would play havoc if not properly initialized
double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized
// - std::optional (C++17) would probably a better choice.
double length_ = -1.e20;
double length_ { -1.e20 }
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle::Rectangle(double width, double length)
: width_(width),
length_(length)
{ }
```
%% Cell type:code id: tags:
``` C++17
double Rectangle::GetWidth() const
{
return width_;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle::GetLength() const
{
return length_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle::SetWidth(double x)
{
width_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle::SetLength(double x)
{
length_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle::Print() const
{
std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
class Square : public Rectangle // BAD IDEA!
{
public:
Square(double side_dimension);
};
```
%% Cell type:code id: tags:
``` C++17
Square::Square(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:markdown id: tags:
This is perfectly valid C++ code, and you might be happy with that... Now let's add a free-function that changes the shape of a rectangle:
%% Cell type:code id: tags:
``` C++17
void ModifyRectangle(Rectangle& r)
{
r.SetWidth(r.GetWidth() * 2.);
r.SetLength(r.GetLength() * .5);
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << " ==== RECTANGLE ==== " << std::endl;
Rectangle r(3., 5.);
r.Print();
ModifyRectangle(r); // ok
r.Print();
std::cout << " ==== SQUARE ==== " << std::endl;
Square c(4.);
c.Print(); // ok
ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency!
c.Print(); // urgh...
}
```
%% Cell type:markdown id: tags:
So the language allows you to define this public relationship between `Rectangle` and `Square`, but you can see it is not a very bright idea... (this example is more detailed in item 32 of \cite{Meyers2005}).
Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best.
The public inheritance is an application of the [Liskov substitution principe](https://en.wikipedia.org/wiki/Liskov_substitution_principle).
We will now "fix" our `Square` problem with two different idioms: private inheritance and composition.
### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance
What you might look at in fact is **private** inheritance, in which all the inherited attributes are considered private:
%% Cell type:code id: tags:
``` C++17
class Square2 : private Rectangle
{
public:
Square2(double side_dimension);
};
```
%% Cell type:code id: tags:
``` C++17
Square2::Square2(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
Square2 square(4.);
square.SetWidth(5.); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
And from there:
%% Cell type:code id: tags:
``` C++17
{
Square2 square(4.);
ModifyRectangle(square); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept.
And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy):
%% Cell type:code id: tags:
``` C++17
class Square3 : private Rectangle
{
public:
Square3(double side_dimension);
void SetSideDimension(double x);
using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle
};
```
%% Cell type:code id: tags:
``` C++17
Square3::Square3(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:code id: tags:
``` C++17
void Square3::SetSideDimension(double x)
{
SetWidth(x); // the methods from Rectangle, accessible privately
SetLength(x);
}
```
%% Cell type:code id: tags:
``` C++17
{
Square3 square(4.);
square.Print();
square.SetSideDimension(3.);
square.Print();
}
```
%% Cell type:markdown id: tags:
### CONTAINS-A relationship of composition
Private inheritance is not the only way to provide this kind of behaviour; it is possible as well to use **composition**, which is defining a data attribute object that handles part of the computation.
I won't dwelve into too much details here, but the general idea is that composition is often preferable as the binding is less strong and you should strive for the looser relationship possible. Here is the same example as above implemented with composition:
%% Cell type:code id: tags:
``` C++17
class Square4
{
public:
Square4(double side_dimension);
~Square4();
void Print() const;
void SetSideDimension(double x);
private:
Rectangle& GetRectangle() const;
private:
Rectangle* rectangle_ = nullptr;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle& Square4::GetRectangle() const
{
return *rectangle_;
}
```
%% Cell type:code id: tags:
``` C++17
Square4::Square4(double side_dimension)
{
rectangle_ = new Rectangle(side_dimension, side_dimension);
}
```
%% Cell type:code id: tags:
``` C++17
Square4::~Square4()
{
delete rectangle_;
}
```
%% Cell type:code id: tags:
``` C++17
void Square4::SetSideDimension(double x)
{
auto& rectangle = GetRectangle();
rectangle.SetWidth(x);
rectangle.SetLength(x);
}
```
%% Cell type:code id: tags:
``` C++17
void Square4::Print() const
{
GetRectangle().Print();
}
```
%% Cell type:code id: tags:
``` C++17
{
Square4 square(4.);
square.Print();
square.SetSideDimension(3.);
square.Print();
}
```
%% Cell type:markdown id: tags:
You may have noticed the data attribute is stored as a pointer: this avoids issues related to the initialization of the object when it uses a non-default constructor. This doesn't mean using directly an object is impossible, just that extra conditions must be fulfilled in this case.
%% Cell type:markdown id: tags:
### Private inheritance vs composition
So what are the pros and cons of private inheritance and composition?
- No granularity with private inheritance: you get all the interface available (privately) in the derived class. On the other hand, with composition you can choose which method you want to expose publicly in your new class (only `Print()` here).
- But composition is more verbosy: if you want most of the interface, you need for each method to define a method which under the hood calls your data attribute (as did `Square4::Print()` above). So in you need most of the API of the base class private inheritance is less work.
As indicated before, I tend to use more composition, but it's nice anyway to know both are available.
%% Cell type:markdown id: tags:
## Protected status
Our discussion about public and private inheritance has highlighted the importance of the propagation of status of public members:
* With public inheritance, public members of the base class remains public.
* With private inheritance, public members of the base class become private.
So far, we have not talked about what happens to private members... Now is the time; let's find out!
%% Cell type:code id: tags:
``` C++17
class BaseClass
{
public:
BaseClass() = default;
private:
void DoNothing() { }
};
```
%% Cell type:code id: tags:
``` C++17
class DerivedClass : public BaseClass
{
public:
DerivedClass();
};
```
%% Cell type:code id: tags:
``` C++17
DerivedClass::DerivedClass()
{
DoNothing(); // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
So a private method is not accessible, even to the derived class. If you need such a functionality (and you should!), there is actually a third keyword: **protected**. This status means basically private except for the derived classes:
%% Cell type:code id: tags:
``` C++17
class BaseClass2
{
public:
BaseClass2() = default;
protected:
void DoNothing() { }
};
```
%% Cell type:code id: tags:
``` C++17
class DerivedClass2 : public BaseClass2
{
public:
DerivedClass2();
};
```
%% Cell type:code id: tags:
``` C++17
DerivedClass2::DerivedClass2()
{
DoNothing(); // Ok
}
```
%% Cell type:code id: tags:
``` C++17
auto object = DerivedClass2();
```
%% Cell type:code id: tags:
``` C++17
object.DoNothing(); // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`.
%% Cell type:markdown id: tags:
There is as well a **protected** status for inheritance even if I must admit I have never needed it. A contributor to [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance) sumed this up with this nice cheatsheet:
%% Cell type:markdown id: tags:
![Diagram](Images/PublicProtectedPrivateDiagram.jpg "Inheritance diagram, courtesy of [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance)")
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers2005" href="#call-Meyers2005">Meyers, 2005</a>) Scott Meyers, ``_Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)_'', 2005.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Polymorphism](./7-polymorphism.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Polymorphism" data-toc-modified-id="Polymorphism-1">Polymorphism</a></span><ul class="toc-item"><li><span><a href="#Naïve-approach-to-underline-the-need" data-toc-modified-id="Naïve-approach-to-underline-the-need-1.1">Naïve approach to underline the need</a></span></li><li><span><a href="#virtual-keyword" data-toc-modified-id="virtual-keyword-1.2"><code>virtual</code> keyword</a></span></li><li><span><a href="#virtual-work-only-with-pointers-or-references" data-toc-modified-id="virtual-work-only-with-pointers-or-references-1.3"><code>virtual</code> work only with pointers or references</a></span></li><li><span><a href="#Abstract-class-and-pure-virtual-methods" data-toc-modified-id="Abstract-class-and-pure-virtual-methods-1.4">Abstract class and pure virtual methods</a></span></li><li><span><a href="#override-keyword" data-toc-modified-id="override-keyword-1.5">override keyword</a></span></li><li><span><a href="#Cost-of-virtuality" data-toc-modified-id="Cost-of-virtuality-1.6">Cost of virtuality</a></span></li></ul></li><li><span><a href="#dynamic_cast" data-toc-modified-id="dynamic_cast-2"><code>dynamic_cast</code></a></span></li><li><span><a href="#final-keyword" data-toc-modified-id="final-keyword-3"><code>final</code> keyword</a></span></li><li><span><a href="#Virtual-destructor-and-the-slicing-effect" data-toc-modified-id="Virtual-destructor-and-the-slicing-effect-4">Virtual destructor and the slicing effect</a></span><ul class="toc-item"><li><span><a href="#Good-practice:-should-my-destructor-be-virtual?" data-toc-modified-id="Good-practice:-should-my-destructor-be-virtual?-4.1">Good practice: should my destructor be virtual?</a></span></li></ul></li><li><span><a href="#Good-practice:-never-call-a-virtual-method-in-a-constructor" data-toc-modified-id="Good-practice:-never-call-a-virtual-method-in-a-constructor-5">Good practice: never call a virtual method in a constructor</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Polymorphism
### Naïve approach to underline the need
[So far](./6-inheritance.ipynb), I have not yet shown how objects could be stored in the same container, which was a justification I gave when introducing inheritance.
The first idea would be to cram all items in a container which type is the base class:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
class NotPolymorphicVehicle
{
public:
NotPolymorphicVehicle() = default;
};
```
%% Cell type:code id: tags:
``` C++17
#include <string>
class NotPolymorphicCar : public NotPolymorphicVehicle
{
public:
NotPolymorphicCar();
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void NotPolymorphicCar::Print() const
{
std::cout << "I'm a car!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <string>
class NotPolymorphicBicycle : public NotPolymorphicVehicle
{
public:
NotPolymorphicBicycle();
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void NotPolymorphicBicycle::Print() const
{
std::cout << "I'm a bike!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
NotPolymorphicBicycle b;
NotPolymorphicCar c;
NotPolymorphicVehicle list[2] = { b, c };
list[0].Print(); // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
The issue here is that the objects are stored as `NotPolymorphicVehicle`, and this class doesn't know any method called `Print()`, even if all its children do.
Defining `Print()` in the base class would not work either:
%% Cell type:code id: tags:
``` C++17
class AlsoNotPolymorphicVehicle
{
public:
AlsoNotPolymorphicVehicle() = default;
void Print() const;
};
class AlsoNotPolymorphicCar : public AlsoNotPolymorphicVehicle
{
public:
AlsoNotPolymorphicCar() = default;
void Print() const;
};
class AlsoNotPolymorphicBicycle : public AlsoNotPolymorphicVehicle
{
public:
AlsoNotPolymorphicBicycle() = default;
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void AlsoNotPolymorphicVehicle::Print() const
{
std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void AlsoNotPolymorphicCar::Print() const
{
std::cout << "I'm a bike!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void AlsoNotPolymorphicBicycle::Print() const
{
std::cout << "I'm a bike!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
AlsoNotPolymorphicBicycle b;
AlsoNotPolymorphicCar c;
AlsoNotPolymorphicVehicle list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
list[i].Print(); // No compilation error, but clearly not what we intended...
}
```
%% Cell type:markdown id: tags:
So far, the perspectives aren't rosy:
* The base class needs to know all the methods beforehand: `Print()` had to be defined in the base class to make it compile.
* And even so, the result was clearly not what we hoped for: the dumb value provided in the base class was actually returned.
### `virtual ` keyword
The second point is easy to solve: there is a dedicated keyword named **virtual** in the language that may qualify a method and tell it is likely to be adapted or superseded in a derived class.
Almost all methods might be virtual, with very few exceptions:
* Static methods
* Constructors: no constructor can be virtual, and even more than that using a virtual method within a constructor won't work as expected.
* Methods with template arguments (we'll [come to that](../4-Templates/1-Intro.ipynb#Function-templates-(or-methods)) ...)
That means even the destructor may be virtual (and probably should - we'll go back to that as well...).
%% Cell type:code id: tags:
``` C++17
class PolymorphicButClumsyVehicle
{
public:
PolymorphicButClumsyVehicle() = default;
virtual void Print() const;
};
class PolymorphicButClumsyCar : public PolymorphicButClumsyVehicle
{
public:
PolymorphicButClumsyCar() = default;
virtual void Print() const;
};
class PolymorphicButClumsyBicycle : public PolymorphicButClumsyVehicle
{
public:
PolymorphicButClumsyBicycle() = default;
virtual void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicButClumsyVehicle::Print() const // Please notice: no `virtual` on definition!
{
std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicButClumsyCar::Print() const
{
std::cout << "I am a car!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicButClumsyBicycle::Print() const
{
std::cout << "I am a bike!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### `virtual` work only with pointers or references
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
PolymorphicButClumsyBicycle b;
PolymorphicButClumsyCar c;
PolymorphicButClumsyVehicle list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
list[i].Print(); // No compilation error, but clearly not what we intended...
}
```
%% Cell type:markdown id: tags:
Still not what was intended... That's because when you use the objects directly as we do here, **static binding** is used: the definitions seen in the base class are used directly because the resolution occurs at compilation time.
To use the **dynamic binding**, we need to use either references or pointers. Let's do that:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle;
PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar;
PolymorphicButClumsyVehicle* list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
list[i]->Print();
delete b;
delete c;
}
```
%% Cell type:markdown id: tags:
The fact that a `PolymorphicButClumsyVehicle` pointer is able to properly call the derived classes version is what is called **polymorphism**; it's at runtime that the decision to call `PolymorphicButClumsyVehicle::Print()` or `PolymorphicButClumsyCar::Print()` is taken.
We're nonetheless not completely done yet:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
PolymorphicButClumsyVehicle* v = new PolymorphicButClumsyVehicle;
PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle;
PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar;
PolymorphicButClumsyVehicle* list[3] = { v, b, c };
for (auto i = 0ul; i < 3ul; ++i)
list[i]->Print(); // No compilation error, but clearly not what we intended...
delete v;
delete b;
delete c;
}
```
%% Cell type:markdown id: tags:
### Abstract class and pure virtual methods
Our issue here is that `PolymorphicButClumsyVehicle` has no business being instantiated directly: it is merely an **abstract class** which should never be instantiated but is there to provide a skeleton to more substantiated derived classes.
The mechanism to indicate that is to provide at least one **pure virtual method**: a method which prototype is given in the base class but that **must** be overriden in derived classes (at least if you want them to become concrete). The syntax to do so is to add `= 0` after the prototype.
%% Cell type:code id: tags:
``` C++17
class PolymorphicVehicle
{
public:
PolymorphicVehicle() = default;
virtual void Print() const = 0; // the only change from PolymorphicButClumsyVehicle!
};
class PolymorphicCar : public PolymorphicVehicle
{
public:
PolymorphicCar() = default;
virtual void Print() const;
};
class PolymorphicBicycle : public PolymorphicVehicle
{
public:
PolymorphicBicycle() = default;
virtual void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicCar::Print() const
{
std::cout << "I am a car!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicBicycle::Print() const
{
std::cout << "I am a bike!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
PolymorphicVehicle v; // Compilation error: you can't instantiate an abstract class!
}
```
%% Cell type:markdown id: tags:
But the following is fine:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
PolymorphicVehicle* b = new PolymorphicBicycle;
PolymorphicVehicle* c = new PolymorphicCar;
PolymorphicVehicle* list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
list[i]->Print();
delete b;
delete c;
}
```
%% Cell type:markdown id: tags:
**Beware:** You **must** provide a definition for all non pure-virtual methods in your class. Not doing so leads to a somewhat cryptic error at link-time.
You are not required to provide a definition for a pure virtual method, and you won't most of the time... But you might provide one if you want to do so, for instance to provide an optional default instantiation for the method in derived classes:
%% Cell type:code id: tags:
``` C++17
struct VirtualBase
{
virtual void Method() = 0;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void VirtualBase::Method()
{
std::cout << "Default implementation provided in abstract class." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
struct Concrete1 : public VirtualBase
{
virtual void Method() override;
};
```
%% Cell type:code id: tags:
``` C++17
struct Concrete2 : public VirtualBase
{
virtual void Method() override;
};
```
%% Cell type:code id: tags:
``` C++17
void Concrete1::Method()
{
VirtualBase::Method(); // call to the method defined in the base class
std::cout << "This enables providing a base behaviour that might be completed if needed "
"in derived classes, such as here by these lines you are reading!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
void Concrete2::Method()
{
std::cout << "Overriden implementation." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << "====== Concrete 1: uses up the definition provided in base class =====" << std::endl;
Concrete1 concrete1;
concrete1.Method();
std::cout << "\n====== Concrete 2: doesn't use the definition provided in base class =====" << std::endl;
Concrete2 concrete2;
concrete2.Method();
}
```
%% Cell type:markdown id: tags:
### override keyword
We saw in previous section that to make a method virtual we need to add a `virtual` qualifier in front of its prototype.
I put it both in the base class and in the derived one, but in fact it is entirely up to the developer concerning the derived classes:
%% Cell type:code id: tags:
``` C++17
class European
{
public:
European() = default;
virtual void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void European::Print() const
{
std::cout << "I'm European!" << std::endl;
};
```
%% Cell type:code id: tags:
``` C++17
class French : public European
{
public:
French() = default;
void Print() const; // virtual keyword is skipped here - and it's fine by the standard
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void French::Print() const
{
std::cout << "I'm French!" << std::endl;
};
```
%% Cell type:code id: tags:
``` C++17
{
European* european = new European;
european->Print();
European* french = new French;
french->Print();
}
```
%% Cell type:markdown id: tags:
But the drawback doing so is that we may forget the method is virtual... or we might do a typo when writing it! And in this case, the result is not what is expected:
%% Cell type:code id: tags:
``` C++17
class ThirstyFrench : public European
{
public:
ThirstyFrench() = default;
virtual void Pint() const; // typo here! And the optional `virtual` doesn't help avoid it...
};
```
%% Cell type:code id: tags:
``` C++17
void ThirstyFrench::Pint() const
{
std::cout << "I'm French!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
ThirstyFrench* french = new ThirstyFrench;
french->Print();
}
```
%% Cell type:markdown id: tags:
What would be nice is for the compiler to provide a way to secure against such errors... and that exactly what C++ 11 introduced with the `override` keyword. This keyword explicitly says we are declaring an override of a virtual method, and the code won't compile if the prototype doesn't match:
%% Cell type:code id: tags:
``` C++17
class ThirstyButCarefulFrench : public European
{
public:
ThirstyButCarefulFrench() = default;
void Pint() const override; // COMPILATION ERROR!
};
```
%% Cell type:code id: tags:
``` C++17
class ForgetfulFrench : public European
{
public:
ForgetfulFrench() = default;
virtual void Print() override; // COMPILATION ERROR! `const` is missing and therefore prototype doesn't match.
};
```
%% Cell type:markdown id: tags:
### Cost of virtuality
You have to keep in mind there is a small cost related to the virtual behaviour: at each method call the program has to figure out which dynamic type to use. To be honest the true effective cost is quite blurry for me: some says it's not that important (see for instance [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)) while others will say you can't be serious if you're using them. I tend personally to avoid them in the part of my code where I want to crunch numbers fast and I use them preferably in the initialization phase.
## `dynamic_cast`
There is yet a question to ask: what if we want to use a method only defined in the derived class? For instance, if we add an attribute `oil_type_` that is not meaningful for all types of vehicles, can we access it?
%% Cell type:code id: tags:
``` C++17
#include <string>
class PolymorphicVehicle
{
public:
PolymorphicVehicle() = default;
virtual void Print() const = 0;
};
class PolymorphicThermicCar : public PolymorphicVehicle
{
public:
PolymorphicThermicCar(std::string&& oil_type);
virtual void Print() const;
const std::string& GetOilType() const;
private:
std::string oil_type_;
};
```
%% Cell type:code id: tags:
``` C++17
PolymorphicThermicCar::PolymorphicThermicCar(std::string&& oil_type)
: PolymorphicVehicle(),
oil_type_(oil_type)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PolymorphicThermicCar::Print() const
{
std::cout << "I'm a car that uses up " << GetOilType() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
const std::string& PolymorphicThermicCar::GetOilType() const
{
return oil_type_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
PolymorphicVehicle* c = new PolymorphicThermicCar("gazole");
std::cout << "Oil type = " << c->GetOilType() << std::endl; // COMPILATION ERROR: been there before...
}
```
%% Cell type:markdown id: tags:
You may in fact explicitly tells the `c` pointer needs to be interpreted as a `PolymorphicThermicCar`:
**Xeus-cling issue:** Here cling doesn't manage to run it but it is accepted rightfully by a full-fledged compiler (see for instance [@Coliru](http://coliru.stacked-crooked.com/a/22fff15d28c93a17)):
%% Cell type:code id: tags:
``` C++17
// Xeus-cling issue!
#include <iostream>
{
PolymorphicThermicCar* c = new PolymorphicThermicCar("gazole");
PolymorphicVehicle* list[1] = { c }; // Ok!
auto c_corrected = dynamic_cast<PolymorphicThermicCar*>(list[0]);
std::cout << "Oil type = " << c_corrected->GetOilType() << std::endl;
}
```
%% Cell type:markdown id: tags:
So you could devise a way to identify which is the dynamic type of your `PolymorphicVehicle` pointer and cast it dynamically to the rightful type so that extended API offered by the derived class is accessible.
If you find this clunky, you are not alone: by experience if you really need to resort to **dynamic_cast** it's probably your data architecture needs some revision. But maybe the mileage vary for other developers, and it's useful to know the possibility exist.
If you find this clunky, you are not alone: by experience if you really need to resort to **dynamic_cast** it's probably your data architecture needs some revision. But maybe the mileage vary for other developers, and it's useful to know the possibility exists.
%% Cell type:markdown id: tags:
## `final` keyword
If you need to specify a class can't be derived, you may stick a `final` keyword in its declaration (from C++11 onward):
If you need to specify a class that can't be derived, you may stick a `final` keyword in its declaration (from C++11 onward):
%% Cell type:code id: tags:
``` C++17
class UnderivableClass final
{ };
```
%% Cell type:code id: tags:
``` C++17
class ITryAnyway : public UnderivableClass // COMPILATION ERROR!
{ };
```
%% Cell type:markdown id: tags:
The keyword may also be used only for one or several virtual methods:
%% Cell type:code id: tags:
``` C++17
struct DerivableClass
{
virtual void Method1();
virtual void Method2();
};
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClassFirstLevel : public DerivableClass
{
virtual void Method1() override final;
virtual void Method2() override;
};
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClassSecondLevel : public DerivedClassFirstLevel
{
virtual void Method2() override; // ok
};
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClassSecondLevelIgnoreFinal : public DerivedClassFirstLevel
{
virtual void Method1() override; // COMPILATION ERROR!
};
```
%% Cell type:markdown id: tags:
## Virtual destructor and the slicing effect
I indicated earlier that destructors might be virtual, but in fact I should have said than for most of the non final classes the destructor should be virtual:
%% Cell type:code id: tags:
``` C++17
struct BaseClass3
{
BaseClass3();
~BaseClass3();
};
```
%% Cell type:code id: tags:
``` C++17
#include <string>
#include <iostream>
BaseClass3::BaseClass3()
{
std::cout << "BaseClass3 constructor" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
BaseClass3::~BaseClass3()
{
std::cout << "BaseClass3 destructor" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClass3 : public BaseClass3
{
DerivedClass3();
~DerivedClass3();
};
```
%% Cell type:code id: tags:
``` C++17
DerivedClass3::DerivedClass3()
{
std::cout << "DerivedClass3 constructor" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
DerivedClass3::~DerivedClass3()
{
std::cout << "DerivedClass3 destructor" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << "Here all should be well: " << std::endl;
{
DerivedClass3 obj;
}
std::cout << std::endl << "But there not so much, see the missing destructor call! " << std::endl;
{
BaseClass3* obj = new DerivedClass3;
delete obj;
}
}
```
%% Cell type:markdown id: tags:
You can see here the derived class destructor is not called! This means if you're for instance deallocating memory in this destructor, the memory will remain unduly allocated.
To circumvent this, you need to declare the destructor virtual in the base class. This way, the derived destructor will be properly called.
This is what is called the **slicing effect**.
### Good practice: should my destructor be virtual?
So to put in a nutshell, 99 % of the time:
* If a class of yours is intended to be inherited from, make its destructor virtual.
* If not, mark it as final.
%% Cell type:markdown id: tags:
## Good practice: never call a virtual method in a constructor
A very important point: I lost time years ago with this because I didn't read carefully enough item 9 of \cite{Meyers2005}...
Due to the way construction occurs, never call a virtual method in a constructor: it won't perform the dynamic binding as you would like it to.
%% Cell type:code id: tags:
``` C++17
#include <string>
struct BaseClass4
{
BaseClass4();
virtual std::string ClassName() const;
};
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClass4 : public BaseClass4
{
DerivedClass4() = default;;
DerivedClass4() = default;
virtual std::string ClassName() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
BaseClass4::BaseClass4()
{
std::cout << "Hello! I'm " << ClassName() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
std::string BaseClass4::ClassName() const
{
return "BaseClass4";
}
```
%% Cell type:code id: tags:
``` C++17
std::string DerivedClass4::ClassName() const
{
return "DerivedClass4";
}
```
%% Cell type:code id: tags:
``` C++17
{
DerivedClass4 object; // not working by stack allocation...
DerivedClass4* object2 = new DerivedClass4; // neither by heap allocation!
}
```
%% Cell type:markdown id: tags:
There is unfortunately no way to circumvent this; just some hack tactics (see for instance the _VirtualConstructor_ idiom on the [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)).
When I need this functionality, I usually define an `Init()` method to call after the constructor, which includes the calls to virtual methods:
%% Cell type:code id: tags:
``` C++17
#include <string>
struct BaseClass5
{
BaseClass5() = default;
void Init();
virtual std::string ClassName() const;
};
```
%% Cell type:code id: tags:
``` C++17
struct DerivedClass5 : public BaseClass5
{
DerivedClass5() = default;;
DerivedClass5() = default;
virtual std::string ClassName() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void BaseClass5::Init()
{
std::cout << "Hello! I'm " << ClassName() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
std::string BaseClass5::ClassName() const
{
return "BaseClass5";
}
```
%% Cell type:code id: tags:
``` C++17
std::string DerivedClass5::ClassName() const
{
return "DerivedClass5";
}
```
%% Cell type:code id: tags:
``` C++17
{
DerivedClass5 object; // nope by stack allocation
object.Init();
DerivedClass5* object2 = new DerivedClass5; // same by heap allocation
object2->Init();
}
```
%% Cell type:markdown id: tags:
But it's not perfect either: if the end-user forget to call the `Init()` method the class could be ill-constructed (to avoid this either I provide manually a check mechanism or I make sure the class is private stuff not intended to be used directly by the end-user).
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers2005" href="#call-Meyers2005">Meyers, 2005</a>) Scott Meyers, ``_Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)_'', 2005.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment