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
%% 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)
%% 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-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:
## 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 `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
class Vehicle
{
public:
Vehicle(unsigned int Nwheels);
private:
unsigned int Nwheels_ = 0;
};
```
%% Cell type:code id: tags:
``` C++17
Vehicle::Vehicle(unsigned int Nwheels)
: Nwheels_(Nwheels)
{ }
```
%% Cell type:code id: tags:
``` C++17
class Bicycle : public Vehicle
{
public:
Bicycle();
};
```
%% Cell type:code id: tags:
``` C++17
class Car : public Vehicle
{
public:
Car();
};
```
%% Cell type:code id: tags:
``` C++17
Bicycle::Bicycle()
: Vehicle(2)
{ }
```
%% Cell type:code id: tags:
``` C++17
Car::Car()
: Vehicle(4)
{ }
```
%% 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 inheritance
It is possible for a class to inherit from several parents:
%% Cell type:code id: tags:
``` C++17
class BlueObjects
{
public:
BlueObjects() = default;
}
```
%% Cell type:code id: tags:
``` C++17
class BlueCar : public Car, public BlueObjects
{
public:
BlueCar() = default; // Ok: default public constructors from both parents are called
};
```
%% Cell type:code id: tags:
``` C++17
class BlueVehicle : public Vehicle, public BlueObjects
{
public:
BlueVehicle(int Nwheels); // Can't call default one: no default constructor for Vehicle!
};
```
%% Cell type:code id: tags:
``` C++17
BlueVehicle::BlueVehicle(int Nwheels)
: Vehicle(Nwheels), // mandatory call of the constructor
BlueObjects() // not mandatory
{ }
```
%% 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;
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 better one.
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.
### 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:
%% 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);
}
```
%% Cell type:markdown id: tags:
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):
%% 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:
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()
}
```
%% 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: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:
## Polymorphism
### 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.
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
class Vehicle2
{
public:
Vehicle2() = default;
};
```
%% Cell type:code id: tags:
``` C++17
class Car2 : public Vehicle2
{
public:
Car2() = default;
int Nwheels() const; // logically should be static if constant for all Cars2
};
```
%% Cell type:code id: tags:
``` C++17
int Car2::Nwheels() const
{
return 4;
}
```
%% Cell type:code id: tags:
``` C++17
class Bicycle2 : public Vehicle2
{
public:
Bicycle2() = default;