Mentions légales du service

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

Insist on the = 0 for pure virtual case, and underline it is nonetheless...

Insist on the = 0 for pure virtual case, and underline it is nonetheless possible to provide an implementation for a pure virtual method.
parent a22d9799
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [Inheritance and polymorphism](/notebooks/2-ObjectProgramming/6-inheritance.ipynb) # [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [Inheritance and polymorphism](/notebooks/2-ObjectProgramming/6-inheritance.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1> <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-inheritance" data-toc-modified-id="Multiple-inheritance-1.1">Multiple 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></ul></li><li><span><a href="#Protected-status" data-toc-modified-id="Protected-status-3">Protected status</a></span></li><li><span><a href="#Polymorphism" data-toc-modified-id="Polymorphism-4">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-4.1">Naïve approach to underline the need</a></span></li><li><span><a href="#virtual-keyword" data-toc-modified-id="virtual-keyword-4.2"><code>virtual</code> keyword</a></span></li><li><span><a href="#override-qualifier" data-toc-modified-id="override-qualifier-4.3"><code>override</code> qualifier</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-4.4"><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-4.5">Abstract class and pure virtual methods</a></span></li><li><span><a href="#Cost-of-virtuality" data-toc-modified-id="Cost-of-virtuality-4.6">Cost of virtuality</a></span></li></ul></li><li><span><a href="#dynamic_cast" data-toc-modified-id="dynamic_cast-5"><code>dynamic_cast</code></a></span></li><li><span><a href="#final-keyword" data-toc-modified-id="final-keyword-6"><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-7">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?-7.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-8">Good practice: never call a virtual method in a constructor</a></span></li></ul></div> <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-inheritance" data-toc-modified-id="Multiple-inheritance-1.1">Multiple 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></ul></li><li><span><a href="#Protected-status" data-toc-modified-id="Protected-status-3">Protected status</a></span></li><li><span><a href="#Polymorphism" data-toc-modified-id="Polymorphism-4">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-4.1">Naïve approach to underline the need</a></span></li><li><span><a href="#virtual-keyword" data-toc-modified-id="virtual-keyword-4.2"><code>virtual</code> keyword</a></span></li><li><span><a href="#override-qualifier" data-toc-modified-id="override-qualifier-4.3"><code>override</code> qualifier</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-4.4"><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-4.5">Abstract class and pure virtual methods</a></span></li><li><span><a href="#Cost-of-virtuality" data-toc-modified-id="Cost-of-virtuality-4.6">Cost of virtuality</a></span></li></ul></li><li><span><a href="#dynamic_cast" data-toc-modified-id="dynamic_cast-5"><code>dynamic_cast</code></a></span></li><li><span><a href="#final-keyword" data-toc-modified-id="final-keyword-6"><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-7">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?-7.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-8">Good practice: never call a virtual method in a constructor</a></span></li></ul></div>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction to inheritance ## 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. 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 `std::vector` would be impossible. 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 `std::vector` would be impossible.
The idea of inheritance is to provide a **base class** from which our classes are derived: The idea of inheritance is to provide a **base class** from which our classes are derived:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle class Vehicle
{ {
public: public:
Vehicle(unsigned int Nwheels); Vehicle(unsigned int Nwheels);
private: private:
unsigned int Nwheels_ = 0; unsigned int Nwheels_ = 0;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Vehicle::Vehicle(unsigned int Nwheels) Vehicle::Vehicle(unsigned int Nwheels)
: Nwheels_(Nwheels) : Nwheels_(Nwheels)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Bicycle : public Vehicle class Bicycle : public Vehicle
{ {
public: public:
Bicycle(); Bicycle();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Car : public Vehicle class Car : public Vehicle
{ {
public: public:
Car(); Car();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Bicycle::Bicycle() Bicycle::Bicycle()
: Vehicle(2) : Vehicle(2)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Car::Car() Car::Car()
: Vehicle(4) : Vehicle(4)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A bit of syntax first: 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. * 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 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). 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). 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 inheritance ### Multiple inheritance
It is possible for a class to inherit from several parents: It is possible for a class to inherit from several parents:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class BlueObjects class BlueObjects
{ {
public: public:
BlueObjects() = default; BlueObjects() = default;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class BlueCar : public Car, public BlueObjects class BlueCar : public Car, public BlueObjects
{ {
public: public:
BlueCar() = default; // Ok: default public constructors from both parents are called BlueCar() = default; // Ok: default public constructors from both parents are called
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class BlueVehicle : public Vehicle, public BlueObjects class BlueVehicle : public Vehicle, public BlueObjects
{ {
public: public:
BlueVehicle(int Nwheels); // Can't call default one: no default constructor for Vehicle! BlueVehicle(int Nwheels); // Can't call default one: no default constructor for Vehicle!
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
BlueVehicle::BlueVehicle(int Nwheels) BlueVehicle::BlueVehicle(int Nwheels)
: Vehicle(Nwheels), // mandatory call of the constructor : Vehicle(Nwheels), // mandatory call of the constructor
BlueObjects() // not mandatory BlueObjects() // not mandatory
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Public inheritance, private inheritance and composition ## Public inheritance, private inheritance and composition
### IS-A relationship of public inheritance ### 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. 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: This might seem trivial, but is not as obvious at it seems. Let's consider a counter-intuitive example:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
class Rectangle class Rectangle
{ {
public: public:
Rectangle(double width, double length); Rectangle(double width, double length);
void SetWidth(double x); void SetWidth(double x);
void SetLength(double x); void SetLength(double x);
double GetWidth() const; double GetWidth() const;
double GetLength() const; double GetLength() const;
void Print() const; void Print() const;
private: private:
double width_ = -1.e20; double width_ = -1.e20;
double length_ = -1.e20; double length_ = -1.e20;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Rectangle::Rectangle(double width, double length) Rectangle::Rectangle(double width, double length)
: width_(width), : width_(width),
length_(length) length_(length)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
double Rectangle::GetWidth() const double Rectangle::GetWidth() const
{ {
return width_; return width_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
double Rectangle::GetLength() const double Rectangle::GetLength() const
{ {
return length_; return length_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Rectangle::SetWidth(double x) void Rectangle::SetWidth(double x)
{ {
width_ = x; width_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Rectangle::SetLength(double x) void Rectangle::SetLength(double x)
{ {
length_ = x; length_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void Rectangle::Print() const void Rectangle::Print() const
{ {
std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl; std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Square : public Rectangle // BAD IDEA! class Square : public Rectangle // BAD IDEA!
{ {
public: public:
Square(double side_dimension); Square(double side_dimension);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Square::Square(double side_dimension) Square::Square(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
void ModifyRectangle(Rectangle& r) void ModifyRectangle(Rectangle& r)
{ {
r.SetWidth(r.GetWidth() * 2.); r.SetWidth(r.GetWidth() * 2.);
r.SetLength(r.GetLength() * .5); r.SetLength(r.GetLength() * .5);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::cout << " ==== RECTANGLE ==== " << std::endl; std::cout << " ==== RECTANGLE ==== " << std::endl;
Rectangle r(3., 5.); Rectangle r(3., 5.);
r.Print(); r.Print();
ModifyRectangle(r); // ok ModifyRectangle(r); // ok
r.Print(); r.Print();
std::cout << " ==== SQUARE ==== " << std::endl; std::cout << " ==== SQUARE ==== " << std::endl;
Square c(4.); Square c(4.);
c.Print(); // ok c.Print(); // ok
ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency! ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency!
c.Print(); // urgh... c.Print(); // urgh...
} }
``` ```
%% Cell type:markdown id: tags: %% 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}). 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 better one. 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 better one.
The public inheritance is an application of the [Liskov substitution principe](https://en.wikipedia.org/wiki/Liskov_substitution_principle). 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 methods: private inheritance and composition. We will now "fix" our `Square` problem with two different methods: private inheritance and composition.
### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance ### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance
What you might look at in fact is for **private** inheritance, in which all the inherited attributes are considered private: What you might look at in fact is for **private** inheritance, in which all the inherited attributes are considered private:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Square2 : private Rectangle class Square2 : private Rectangle
{ {
public: public:
Square2(double side_dimension); Square2(double side_dimension);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Square2::Square2(double side_dimension) Square2::Square2(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Square2 square(4.); Square2 square(4.);
square.SetWidth(5.); // COMPILATION ERROR! square.SetWidth(5.); // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And from there: And from there:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Square2 square(4.); Square2 square(4.);
ModifyRectangle(square); ModifyRectangle(square);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So this way, there is no assumption a `Square2` might pass any calls a `Rectangle` would accept. So this way, there is no assumption a `Square2` might pass any calls a `Rectangle` would accept.
And of course the point is to avoid refining 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): And of course the point is to avoid refining 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Square3 : private Rectangle class Square3 : private Rectangle
{ {
public: public:
Square3(double side_dimension); Square3(double side_dimension);
void SetSideDimension(double x); void SetSideDimension(double x);
using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Square3::Square3(double side_dimension) Square3::Square3(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Square3::SetSideDimension(double x) void Square3::SetSideDimension(double x)
{ {
SetWidth(x); // the methods from Rectangle, accessible privately SetWidth(x); // the methods from Rectangle, accessible privately
SetLength(x); SetLength(x);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Square3 square(4.); Square3 square(4.);
square.Print(); square.Print();
square.SetSideDimension(3.); square.SetSideDimension(3.);
square.Print(); square.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### CONTAINS-A relationship of composition ### 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. 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Square4 class Square4
{ {
public: public:
Square4(double side_dimension); Square4(double side_dimension);
~Square4(); ~Square4();
void Print() const; void Print() const;
void SetSideDimension(double x); void SetSideDimension(double x);
private: private:
Rectangle& GetRectangle() const; Rectangle& GetRectangle() const;
private: private:
Rectangle* rectangle_ = nullptr; Rectangle* rectangle_ = nullptr;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Rectangle& Square4::GetRectangle() const Rectangle& Square4::GetRectangle() const
{ {
return *rectangle_; return *rectangle_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Square4::Square4(double side_dimension) Square4::Square4(double side_dimension)
{ {
rectangle_ = new Rectangle(side_dimension, side_dimension); rectangle_ = new Rectangle(side_dimension, side_dimension);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Square4::~Square4() Square4::~Square4()
{ {
delete rectangle_; delete rectangle_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Square4::SetSideDimension(double x) void Square4::SetSideDimension(double x)
{ {
auto& rectangle = GetRectangle(); auto& rectangle = GetRectangle();
rectangle.SetWidth(x); rectangle.SetWidth(x);
rectangle.SetLength(x); rectangle.SetLength(x);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
void Square4::Print() const void Square4::Print() const
{ {
GetRectangle().Print(); GetRectangle().Print();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Square4 square(4.); Square4 square(4.);
square.Print(); square.Print();
square.SetSideDimension(3.); square.SetSideDimension(3.);
square.Print(); square.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% 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. 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: %% Cell type:markdown id: tags:
So what are the pros and cons of private inheritance and 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). - 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. - 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. As indicated before, I tend to use more composition, but it's nice anyway to know both are available.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Protected status ## Protected status
Our discussion about public and private inheritance has highlighted the importance of the propagation of status of public members: 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 public inheritance, public members of the base class remains public.
* With private inheritance, public members of the base class become private. * 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! 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class BaseClass class BaseClass
{ {
public: public:
BaseClass() = default; BaseClass() = default;
private: private:
void DoNothing() { } void DoNothing() { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class DerivedClass : public BaseClass class DerivedClass : public BaseClass
{ {
public: public:
DerivedClass(); DerivedClass();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
DerivedClass::DerivedClass() DerivedClass::DerivedClass()
{ {
DoNothing() DoNothing()
} }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class BaseClass2 class BaseClass2
{ {
public: public:
BaseClass2() = default; BaseClass2() = default;
protected: protected:
void DoNothing() { } void DoNothing() { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class DerivedClass2 : public BaseClass2 class DerivedClass2 : public BaseClass2
{ {
public: public:
DerivedClass2(); DerivedClass2();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
DerivedClass2::DerivedClass2() DerivedClass2::DerivedClass2()
{ {
DoNothing(); // Ok DoNothing(); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% 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)") ![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: %% Cell type:markdown id: tags:
## Polymorphism ## Polymorphism
### Naïve approach to underline the need ### Naïve approach to underline the need
So far, I have not yet shown how objects could be stored in the same container, which was a justification I gave when introducing inheritance. So far, 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: The first idea would be to cram all items in a container which type is the base class:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle2 class Vehicle2
{ {
public: public:
Vehicle2() = default; Vehicle2() = default;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Car2 : public Vehicle2 class Car2 : public Vehicle2
{ {
public: public:
Car2() = default; Car2() = default;
int Nwheels() const; // logically should be static if constant for all Cars2 int Nwheels() const; // logically should be static if constant for all Cars2
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Car2::Nwheels() const int Car2::Nwheels() const
{ {
return 4; return 4;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Bicycle2 : public Vehicle2 class Bicycle2 : public Vehicle2
{ {
public: public:
Bicycle2() = default; Bicycle2() = default;
int Nwheels() const; // logically should be static if constant for all Cars2 int Nwheels() const; // logically should be static if constant for all Cars2
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Bicycle2::Nwheels() const int Bicycle2::Nwheels() const
{ {
return 2; return 2;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Bicycle2 b; Bicycle2 b;
Car2 c; Car2 c;
Vehicle2 list[2] = { b, c }; Vehicle2 list[2] = { b, c };
list[0].Nwheels(); // COMPILATION ERROR list[0].Nwheels(); // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The issue here is that the objects are stored as `Vehicle2`, and this class doesn't know any method called `Nwheels()`, even if all its children do. The issue here is that the objects are stored as `Vehicle2`, and this class doesn't know any method called `Nwheels()`, even if all its children do.
Defining `Nwheels()` in the base class would not work either: Defining `Nwheels()` in the base class would not work either:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle3 class Vehicle3
{ {
public: public:
Vehicle3() = default; Vehicle3() = default;
int Nwheels() const; int Nwheels() const;
}; };
class Car3 : public Vehicle3 class Car3 : public Vehicle3
{ {
public: public:
Car3() = default; Car3() = default;
int Nwheels() const; // logically should be static if constant for all Cars2 int Nwheels() const; // logically should be static if constant for all Cars2
}; };
class Bicycle3 : public Vehicle3 class Bicycle3 : public Vehicle3
{ {
public: public:
Bicycle3() = default; Bicycle3() = default;
int Nwheels() const; // logically should be static if constant for all Cars2 int Nwheels() const; // logically should be static if constant for all Cars2
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Vehicle3::Nwheels() const int Vehicle3::Nwheels() const
{ {
return 0; // Stupid value, but which one should we pick up??? return 0; // Stupid value, but which one should we pick up???
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Car3::Nwheels() const int Car3::Nwheels() const
{ {
return 4; return 4;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Bicycle3::Nwheels() const int Bicycle3::Nwheels() const
{ {
return 2; return 2;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Bicycle3 b; Bicycle3 b;
Car3 c; Car3 c;
Vehicle3 list[2] = { b, c }; Vehicle3 list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // No compilation error, but clearly not what we intended... std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // No compilation error, but clearly not what we intended...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So far, the perspectives aren't rosy: So far, the perspectives aren't rosy:
* The base class needs to know all the methods beforehand: `Nwheels()` had to be defined in the base class to make it compile. * The base class needs to know all the methods beforehand: `Nwheels()` 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. * 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 ### `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. 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: Almost all methods might be virtual, with very few exceptions:
* Static methods * 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. * 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](/notebooks/4-Templates/1-Intro.ipynb#Function-templates-(or-methods))...) * Methods with template arguments (we'll [come to that](/notebooks/4-Templates/1-Intro.ipynb#Function-templates-(or-methods))...)
That means even the destructor may be virtual (we'll go back to that as well...). That means even the destructor may be virtual (we'll go back to that as well...).
I will also introduce and explain **override** after the example, I will also introduce and explain **override** after the example,
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle4 class Vehicle4
{ {
public: public:
Vehicle4() = default; Vehicle4() = default;
virtual int Nwheels() const; virtual int Nwheels() const;
}; };
class Car4 : public Vehicle4 class Car4 : public Vehicle4
{ {
public: public:
Car4() = default; Car4() = default;
virtual int Nwheels() const override; virtual int Nwheels() const override;
}; };
class Bicycle4 : public Vehicle4 class Bicycle4 : public Vehicle4
{ {
public: public:
Bicycle4() = default; Bicycle4() = default;
virtual int Nwheels() const override; virtual int Nwheels() const override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Vehicle4::Nwheels() const // notice the absence of `virtual` keyword. int Vehicle4::Nwheels() const // notice the absence of `virtual` keyword.
{ {
return 0; // Stupid value, but which one should we pick up??? return 0; // Stupid value, but which one should we pick up???
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Car4::Nwheels() const int Car4::Nwheels() const
{ {
return 4; return 4;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Bicycle4::Nwheels() const int Bicycle4::Nwheels() const
{ {
return 2; return 2;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A few musings before trying to use it: A few musings before trying to use it:
* **virtual** should be only declared; it is not repeated in the method definition. Adding it there is a compilation error! * **virtual** should be only declared; it is not repeated in the method definition. Adding it there is a compilation error!
* Same for **override** (see explanation for this one below) * Same for **override** (see explanation for this one below)
* **virtual** keyword is mandatory only in the base class. I advise to write it in the derived classes, but it is not a language requirement. * **virtual** keyword is mandatory only in the base class. I advise to write it in the derived classes, but it is not a language requirement.
* Prior to C++ 11, there was the risk you make a mistake in the exact prototype; for instance the following implementation would be valid and accepted both by the compiler and at runtime (but don't expect the correct behaviour!): * Prior to C++ 11, there was the risk you make a mistake in the exact prototype; for instance the following implementation would be valid and accepted both by the compiler and at runtime (but don't expect the correct behaviour!):
### `override` qualifier ### `override` qualifier
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle4_typo class Vehicle4_typo
{ {
public: public:
Vehicle4_typo() = default; Vehicle4_typo() = default;
virtual int Nwheels() const; virtual int Nwheels() const;
}; };
class Car4_typo : public Vehicle4_typo class Car4_typo : public Vehicle4_typo
{ {
public: public:
Car4_typo() = default; Car4_typo() = default;
virtual int Nwhels() const; // Please notice the missing 'e'! virtual int Nwhels() const; // Please notice the missing 'e'!
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 11 introduced the qualifier **override**, which indicates the method declared in the derived class is meant to be an overriding; if not a compilation error is issued. This is a very welcome behaviour: if at some point you change the prototype of a virtual method, your compiler will yell if you do not adapt accordingly the derived methods if you didn't forget it. C++ 11 introduced the qualifier **override**, which indicates the method declared in the derived class is meant to be an overriding; if not a compilation error is issued. This is a very welcome behaviour: if at some point you change the prototype of a virtual method, your compiler will yell if you do not adapt accordingly the derived methods if you didn't forget it.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Car4_typo_override : public Vehicle4_typo class Car4_typo_override : public Vehicle4_typo
{ {
public: public:
Car4_typo_override() = default; Car4_typo_override() = default;
virtual int Nwhels() const override; // Please notice the missing 'e', that now triggers a compilation error! virtual int Nwhels() const override; // Please notice the missing 'e', that now triggers a compilation error!
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `virtual` work only with pointers or references ### `virtual` work only with pointers or references
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Bicycle4 b; Bicycle4 b;
Car4 c; Car4 c;
Vehicle4 list[2] = { b, c }; Vehicle4 list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // No compilation error, but clearly not what we intended... std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // No compilation error, but clearly not what we intended...
} }
``` ```
%% Cell type:markdown id: tags: %% 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. 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: To use the **dynamic binding**, we need to use either references or pointers. Let's do that:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Bicycle4* b = new Bicycle4; Bicycle4* b = new Bicycle4;
Car4* c = new Car4; Car4* c = new Car4;
Vehicle4* list[2] = { b, c }; // accepted even if `b` and `c` are not (only) Vehicle4 pointers! Vehicle4* list[2] = { b, c }; // accepted even if `b` and `c` are not (only) Vehicle4 pointers!
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl; // Better! std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl; // Better!
delete b; delete b;
delete c; delete c;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The fact that a `Vehicle4` pointer is able to properly call the derived classes version is what is called **polymorphism**; it's at runtime that the decision to call `Car4::Nwheels()` or `Bicycle4::Nwheels()` is taken. The fact that a `Vehicle4` pointer is able to properly call the derived classes version is what is called **polymorphism**; it's at runtime that the decision to call `Car4::Nwheels()` or `Bicycle4::Nwheels()` is taken.
We're nonetheless not completely done yet: We're nonetheless not completely done yet:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Bicycle4* b = new Bicycle4; Bicycle4* b = new Bicycle4;
Car4* c = new Car4; Car4* c = new Car4;
Vehicle4* v = new Vehicle4; Vehicle4* v = new Vehicle4;
Vehicle4* list[3] = { b, c, v }; Vehicle4* list[3] = { b, c, v };
for (auto i = 0ul; i < 3ul; ++i) for (auto i = 0ul; i < 3ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl; std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl;
delete b; delete b;
delete c; delete c;
delete v; delete v;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Abstract class and pure virtual methods ### Abstract class and pure virtual methods
Our issue here is that `Vehicle4` 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. Our issue here is that `Vehicle4` 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. 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Vehicle5 class Vehicle5
{ {
public: public:
Vehicle5() = default; Vehicle5() = default;
virtual int Nwheels() const = 0; // The only change from Vehicle4! virtual int Nwheels() const = 0; // The only change from Vehicle4!
}; };
class Car5 : public Vehicle5 class Car5 : public Vehicle5
{ {
public: public:
Car5() = default; Car5() = default;
virtual int Nwheels() const override; virtual int Nwheels() const override;
}; };
class Bicycle5 : public Vehicle5 class Bicycle5 : public Vehicle5
{ {
public: public:
Bicycle5() = default; Bicycle5() = default;
virtual int Nwheels() const override; virtual int Nwheels() const override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Bicycle5::Nwheels() const int Bicycle5::Nwheels() const
{ {
return 2; return 2;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
int Car5::Nwheels() const int Car5::Nwheels() const
{ {
return 4; return 4;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
Vehicle5 v; // Compilation error: you can't instantiate an abstract class! Vehicle5 v; // Compilation error: you can't instantiate an abstract class!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But the following is fine: But the following is fine:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Bicycle5* b = new Bicycle5; Bicycle5* b = new Bicycle5;
Car5* c = new Car5; Car5* c = new Car5;
Vehicle5* list[2] = { b, c }; // Ok! Vehicle5* list[2] = { b, c }; // Ok!
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl; std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl;
delete b; delete b;
delete c; delete c;
} }
``` ```
%% Cell type:markdown id: tags: %% 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:
### Cost of virtuality ### 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. 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` ## `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? 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
class Car5WithOil : public Vehicle5 class Car5WithOil : public Vehicle5
{ {
public: public:
Car5WithOil(std::string oil_type); Car5WithOil(std::string oil_type);
virtual int Nwheels() const override; virtual int Nwheels() const override;
const std::string& GetOilType() const; const std::string& GetOilType() const;
private: private:
std::string oil_type_ = "undefined"; std::string oil_type_ = "undefined";
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
Car5WithOil::Car5WithOil(std::string oil_type) Car5WithOil::Car5WithOil(std::string oil_type)
: Vehicle5(), : Vehicle5(),
oil_type_(oil_type) oil_type_(oil_type)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
const std::string& Car5WithOil::GetOilType() const const std::string& Car5WithOil::GetOilType() const
{ {
return oil_type_; return oil_type_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
{ {
Vehicle5* c = new Car5WithOil("gazole"); Vehicle5* c = new Car5WithOil("gazole");
std::cout << "Oil type = " << c->GetOilType() << std::endl; // compilation error: been there before... std::cout << "Oil type = " << c->GetOilType() << std::endl; // compilation error: been there before...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may in fact explicitly tells the `c` pointer needs to be interpreted as a `Car5WithOil`: You may in fact explicitly tells the `c` pointer needs to be interpreted as a `Car5WithOil`:
**Xeus-cling issue:** Here cling doesn't manage to run it but it is accepted rightfully by a full-fledged compiler: **Xeus-cling issue:** Here cling doesn't manage to run it but it is accepted rightfully by a full-fledged compiler:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
// Xeus-cling issue! // Xeus-cling issue!
#include <iostream> #include <iostream>
{ {
Car5WithOil* c = new Car5WithOil("gazole"); Car5WithOil* c = new Car5WithOil("gazole");
Bicycle5* b = new Bicycle5; Bicycle5* b = new Bicycle5;
Vehicle5* list[2] = { b, c }; // Ok! Vehicle5* list[2] = { b, c }; // Ok!
auto c_corrected = dynamic_cast<Car5WithOil*>(list[1]); auto c_corrected = dynamic_cast<Car5WithOil*>(list[1]);
std::cout << "Oil type = " << c_corrected->GetOilType() << std::endl; std::cout << "Oil type = " << c_corrected->GetOilType() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So you could devise a way to identify which is the dynamic type of your `Vehicle5` pointer and cast it dynamically to the rightful type so that extended API offered by the derived class is accessible. So you could devise a way to identify which is the dynamic type of your `Vehicle5` 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 exist.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `final` keyword ## `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 can't be derived, you may stick a `final` keyword in its declaration (from C++11 onward):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class UnderivableClass final class UnderivableClass final
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
class ITryAnyway : public UnderivableClass class ITryAnyway : public UnderivableClass
{ }; { };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The keyword may also be used only for one or several virtual methods: The keyword may also be used only for one or several virtual methods:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivableClass struct DerivableClass
{ {
virtual void Method1(); virtual void Method1();
virtual void Method2(); virtual void Method2();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClassFirstLevel : public DerivableClass struct DerivedClassFirstLevel : public DerivableClass
{ {
virtual void Method1() override final; virtual void Method1() override final;
virtual void Method2() override; virtual void Method2() override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClassSecondLevel : public DerivedClassFirstLevel struct DerivedClassSecondLevel : public DerivedClassFirstLevel
{ {
virtual void Method2() override; // ok virtual void Method2() override; // ok
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClassSecondLevelIgnoreFinal : public DerivedClassFirstLevel struct DerivedClassSecondLevelIgnoreFinal : public DerivedClassFirstLevel
{ {
virtual void Method1() override; // compilation error! virtual void Method1() override; // compilation error!
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Virtual destructor and the slicing effect ## 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct BaseClass3 struct BaseClass3
{ {
BaseClass3(); BaseClass3();
~BaseClass3(); ~BaseClass3();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
#include <iostream> #include <iostream>
BaseClass3::BaseClass3() BaseClass3::BaseClass3()
{ {
std::cout << "BaseClass3 constructor" << std::endl; std::cout << "BaseClass3 constructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
BaseClass3::~BaseClass3() BaseClass3::~BaseClass3()
{ {
std::cout << "BaseClass3 destructor" << std::endl; std::cout << "BaseClass3 destructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClass3 : public BaseClass3 struct DerivedClass3 : public BaseClass3
{ {
DerivedClass3(); DerivedClass3();
~DerivedClass3(); ~DerivedClass3();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
DerivedClass3::DerivedClass3() DerivedClass3::DerivedClass3()
{ {
std::cout << "DerivedClass3 constructor" << std::endl; std::cout << "DerivedClass3 constructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
DerivedClass3::~DerivedClass3() DerivedClass3::~DerivedClass3()
{ {
std::cout << "DerivedClass3 destructor" << std::endl; std::cout << "DerivedClass3 destructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
std::cout << "Here all should be well: " << std::endl; std::cout << "Here all should be well: " << std::endl;
{ {
DerivedClass3 obj; DerivedClass3 obj;
} }
std::cout << std::endl << "But there not so much, see the missing destructor call! " << std::endl; std::cout << std::endl << "But there not so much, see the missing destructor call! " << std::endl;
{ {
BaseClass3* obj = new DerivedClass3; BaseClass3* obj = new DerivedClass3;
delete obj; delete obj;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% 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. 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. 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**. This is what is called the **slicing effect**.
### Good practice: should my destructor be virtual? ### Good practice: should my destructor be virtual?
So to put in a nutshell, 99 % of the time: 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 a class of yours is intended to be inherited from, make its destructor virtual.
* If not, mark it as final. * If not, mark it as final.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: never call a virtual method in a constructor ## 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}... 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. 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
struct BaseClass4 struct BaseClass4
{ {
BaseClass4(); BaseClass4();
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClass4 : public BaseClass4 struct DerivedClass4 : public BaseClass4
{ {
DerivedClass4() = default;; DerivedClass4() = default;;
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
BaseClass4::BaseClass4() BaseClass4::BaseClass4()
{ {
std::cout << "Hello! I'm " << ClassName() << std::endl; std::cout << "Hello! I'm " << ClassName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string BaseClass4::ClassName() const std::string BaseClass4::ClassName() const
{ {
return "BaseClass4"; return "BaseClass4";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string DerivedClass4::ClassName() const std::string DerivedClass4::ClassName() const
{ {
return "DerivedClass4"; return "DerivedClass4";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
DerivedClass4 object; // nope by stack allocation DerivedClass4 object; // nope by stack allocation
DerivedClass4* object2 = new DerivedClass4; // same by heap allocation DerivedClass4* object2 = new DerivedClass4; // same by heap allocation
} }
``` ```
%% Cell type:markdown id: tags: %% 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)). 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: 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: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <string> #include <string>
struct BaseClass5 struct BaseClass5
{ {
BaseClass5() = default; BaseClass5() = default;
void Init(); void Init();
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
struct DerivedClass5 : public BaseClass5 struct DerivedClass5 : public BaseClass5
{ {
DerivedClass5() = default;; DerivedClass5() = default;;
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
#include <iostream> #include <iostream>
void BaseClass5::Init() void BaseClass5::Init()
{ {
std::cout << "Hello! I'm " << ClassName() << std::endl; std::cout << "Hello! I'm " << ClassName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string BaseClass5::ClassName() const std::string BaseClass5::ClassName() const
{ {
return "BaseClass5"; return "BaseClass5";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
std::string DerivedClass5::ClassName() const std::string DerivedClass5::ClassName() const
{ {
return "DerivedClass5"; return "DerivedClass5";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` C++17
{ {
DerivedClass5 object; // nope by stack allocation DerivedClass5 object; // nope by stack allocation
object.Init(); object.Init();
DerivedClass5* object2 = new DerivedClass5; // same by heap allocation DerivedClass5* object2 = new DerivedClass5; // same by heap allocation
object2->Init(); object2->Init();
} }
``` ```
%% Cell type:markdown id: tags: %% 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). 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: %% Cell type:markdown id: tags:
# References # 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. (<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: %% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_ © _CNRS 2016_ - _Inria 2018-2019_
_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/)_ _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)_ _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