Mentions légales du service

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

Object programming: introduce corrections suggested by Vincent.

parent cde2626b
Branches
Tags
No related merge requests found
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [TP 1](/notebooks/2-ObjectProgramming/1b-TP.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="#EXERCICE-1:-introduce-Coef-structure" data-toc-modified-id="EXERCICE-1:-introduce-Coef-structure-1">EXERCICE 1: introduce <code>Coef</code> structure</a></span></li></ul></div>
%% Cell type:markdown id: tags:
### EXERCICE 1: introduce `Coef` structure
We will tackle the same problem with a dedicated `struct` called `Coef` which will be used to represent a real by two integers: the numerator and the exponent to the power of 2 used in the denominator.
Create this structure and use it throughout the program.
**NOTE:** The initial file given in the TP section is the solution of the exercice 10 (we drop the extension to write into file).
The expected output is unchanged from the initial file:
````
0.65 ~ 3 / 2^2 (0.75) [error = 15 % for 2 bits]
0.65 ~ 10 / 2^4 (0.625) [error = 4 % for 4 bits]
0.65 ~ 42 / 2^6 (0.65625) [error = 1 % for 6 bits]
0.65 ~ 166 / 2^8 (0.648438) [error = 0 % for 8 bits]
0.35 ~ 3 / 2^3 (0.375) [error = 7 % for 2 bits]
0.35 ~ 11 / 2^5 (0.34375) [error = 2 % for 4 bits]
0.35 ~ 45 / 2^7 (0.351562) [error = 0 % for 6 bits]
0.35 ~ 179 / 2^9 (0.349609) [error = 0 % for 8 bits]
1 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254 / 1000]
2 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119 / 1000]
3 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8 / 1000]
4 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30 / 1000]
5 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2 / 1000]
6 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7 / 1000]
7 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0 / 1000]
8 bits : 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2 / 1000]
````
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018_
_This notebook is an adaptation of a lecture prepared and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [TP 2](/notebooks/2-ObjectProgramming/2b-TP.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="#EXERCICE-12:-introduce-Coef-structure" data-toc-modified-id="EXERCICE-12:-introduce-Coef-structure-1">EXERCICE 12: introduce <code>Coef</code> structure</a></span></li></ul></div>
%% Cell type:markdown id: tags:
### EXERCICE 2: add methods to `Coef`
What we did in exercice 1 is a bit clunky: the object is introduced but we keep spending time fetching the data attribute to act upon them (even initialization to 0 is done manually several times).
What we will do here is to add several methods to `Coef`:
- Transform `compute_power_of_2_approx()` into a method named `compute_approx()`. Instead of modifying a `Coef` argument, the method should act upon the data attributes.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018_
_This notebook is an adaptation of a lecture prepared and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [(Base) constructors and destructor](/notebooks/2-ObjectProgramming/3-constructors-destructor.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-base-constructor" data-toc-modified-id="Introduction-to-base-constructor-1">Introduction to base constructor</a></span></li><li><span><a href="#Initializing-a-reference-data-attribute" data-toc-modified-id="Initializing-a-reference-data-attribute-2">Initializing a reference data attribute</a></span></li><li><span><a href="#Delegating-constructor" data-toc-modified-id="Delegating-constructor-3">Delegating constructor</a></span></li><li><span><a href="#Default-constructor" data-toc-modified-id="Default-constructor-4">Default constructor</a></span></li><li><span><a href="#Good-practice:-data-attribute-initialization" data-toc-modified-id="Good-practice:-data-attribute-initialization-5">Good practice: data attribute initialization</a></span></li><li><span><a href="#Good-practive:-use-explicit-constructors-by-default" data-toc-modified-id="Good-practive:-use-explicit-constructors-by-default-6">Good practive: use <code>explicit</code> constructors by default</a></span></li><li><span><a href="#Destructor" data-toc-modified-id="Destructor-7">Destructor</a></span><ul class="toc-item"><li><span><a href="#Default-destructor" data-toc-modified-id="Default-destructor-7.1">Default destructor</a></span></li></ul></li></ul></div>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-base-constructor" data-toc-modified-id="Introduction-to-base-constructor-1">Introduction to base constructor</a></span></li><li><span><a href="#Initializing-a-reference-data-attribute" data-toc-modified-id="Initializing-a-reference-data-attribute-2">Initializing a reference data attribute</a></span></li><li><span><a href="#Delegating-constructor" data-toc-modified-id="Delegating-constructor-3">Delegating constructor</a></span></li><li><span><a href="#Default-constructor" data-toc-modified-id="Default-constructor-4">Default constructor</a></span></li><li><span><a href="#Good-practice:-data-attribute-initialization" data-toc-modified-id="Good-practice:-data-attribute-initialization-5">Good practice: data attribute initialization</a></span></li><li><span><a href="#Good-practice:-use-explicit-constructors-by-default" data-toc-modified-id="Good-practice:-use-explicit-constructors-by-default-6">Good practice: use <code>explicit</code> constructors by default</a></span></li><li><span><a href="#Destructor" data-toc-modified-id="Destructor-7">Destructor</a></span><ul class="toc-item"><li><span><a href="#Default-destructor" data-toc-modified-id="Default-destructor-7.1">Default destructor</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction to base constructor
In fact, our previous `init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.
Several constructors may be defined for a given class, provided there is no signature overlap.
%% Cell type:code id: tags:
``` C++17
struct Vector6
{
double x_;
double y_;
double z_;
Vector6(); // Constructor
Vector6(double x, double y, double z); // Another constructor
double norm() const;
};
```
%% Cell type:markdown id: tags:
Data attributes may be initialized more efficiently with a special syntax shown below:
%% Cell type:code id: tags:
``` C++17
Vector6::Vector6(double x, double y, double z)
: x_(x), // See the syntax here: `:` to introduce data attributes initialization,,,
y_(y), // and commas to separate the different data attributes.
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector6::Vector6()
: x_(0.), //
y_(0.),
z_(0.)
{ }
```
%% Cell type:code id: tags:
``` C++17
double Vector6::norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector6 v(5., 6., -4.2); // note the creation of an object with a constructor call.
std::cout << v.norm() << std::endl;
}
```
%% Output
8.86792
%% Cell type:markdown id: tags:
**WARNING:** There is a technicality for constructor without arguments: they must be called without parenthesis (the reason is a possible confusion with a [functor](/notebooks/3-Operators/5-Functors.ipynb) - see Item 6 of \cite{Meyers2001} or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector6 v; // no parenthesis here!
std::cout << v.norm() << std::endl;
}
```
%% Output
0
%% Cell type:markdown id: tags:
## Initializing a reference data attribute
We saw above a new syntax to initialize data attributes, but there is no harm defining the same as we did in our former `init()` method. That is however not true when there is a reference data attribute, for which the reference must absolutely be defined with the new syntax:
%% Cell type:code id: tags:
``` C++17
struct First
{
};
```
%% Cell type:code id: tags:
``` C++17
struct Second
{
const First& first_;
Second(const First& first);
};
```
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
{
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
}
```
%% Output
input_line_17:1:9: error: constructor for 'Second' must explicitly initialize the reference member 'first_'
Second::Second(const First& first)
 ^
input_line_16:4:18: note: declared here
const First& first_;
 ^
input_line_17:3:12: error: no viable overloaded '='
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
 ~~~~~~ ^ ~~~~~
input_line_15:1:8: note: candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'const First', but method is not marked const
struct First
 ^
input_line_15:1:8: note: candidate function (the implicit move assignment operator) not viable: 'this' argument has type 'const First', but method is not marked const
Interpreter Error:
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
: first_(first) // OK!
{ }
```
%% Cell type:markdown id: tags:
The reason for this behaviour is that the reference must be defined at the very moment the object is created, and in fact the body of a constructor is run _after_ the actual creation occurs. With the proper syntax, the data is properly initialized in the same time the object is created.
## Delegating constructor
Since C++ 11, it is possible to use a base constructor when defining another constructor:
%% Cell type:code id: tags:
``` C++17
struct Vector7
{
double x_, y_, z_;
Vector7();
Vector7(double xyz);
Vector7(double x, double y, double z);
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector7::Print() const
{
std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Vector7::Vector7()
: x_(-1.),
y_(-1.),
z_(-1.)
{
std::cout << "Calling Vector7 constructor with no arguments." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
Vector7::Vector7(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector7::Vector7(double x)
: Vector7()
{
x_ = x; // As the first constructor is assumed to build fully the object, you can't assign data attributes
// before the body of the constructor.
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << "Constructor with no argument:" << std::endl;
Vector7 v1;
v1.Print();
std::cout << "Constructor with no delegation:" << std::endl;
Vector7 v2(3., 7., 5.);
v2.Print();
std::cout << "Constructor that calls a delegate constructor:" << std::endl;
Vector7 v3(3.);
v3.Print();
}
```
%% Output
Constructor with no argument:
Calling Vector7 constructor with no arguments.
(x, y, z) = (-1, -1, -1)
Constructor with no delegation:
(x, y, z) = (3, 7, 5)
Constructor that calls a delegate constructor:
Calling Vector7 constructor with no arguments.
(x, y, z) = (3, -1, -1)
%% Cell type:markdown id: tags:
## Default constructor
If in a `class` or `struct` no constructor is defined, a default one is assumed: it takes no argument and sports an empty body.
As soon as another constructor is defined, this default constructor no longer exists:
%% Cell type:code id: tags:
``` C++17
struct ClassWithoutConstructor
{ };
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithoutConstructor my_object;
}
```
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithArg
{
ClassWithConstructorWithArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithArg my_object; // COMPILATION ERROR!
}
```
%% Output
input_line_32:3:33: error: no matching constructor for initialization of 'ClassWithConstructorWithArg'
input_line_31:3:33: error: no matching constructor for initialization of 'ClassWithConstructorWithArg'
ClassWithConstructorWithArg my_object; // COMPILATION ERROR!
 ^
input_line_31:1:30: note: candidate constructor not viable: requires single argument 'a', but no arguments were provided
input_line_30:1:30: note: candidate constructor not viable: requires single argument 'a', but no arguments were provided
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
 ^
input_line_30:1:8: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 0 were provided
input_line_29:1:8: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 0 were provided
struct ClassWithConstructorWithArg
 ^
input_line_30:1:8: note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 0 were provided
input_line_29:1:8: note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 0 were provided
Interpreter Error:
%% Cell type:markdown id: tags:
This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well-thought arguments, you do not want your end-user to bypass this with a inconsiderate call to a constructor without arguments!
If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithAndWithoutArg my_object; // OK!
}
```
%% Cell type:markdown id: tags:
## Good practice: data attribute initialization
In a constructor, you are expected to initialize properly all the data attributes. You can't expect a default behaviour if you fail to do so:
%% Cell type:code id: tags:
``` C++17
struct BadlyInitialized
{
int a;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
BadlyInitialized my_object;
std::cout << "Undefined behaviour: no guarantee for the value of the data attribute!: " << my_object.a << std::endl;
}
```
%% Output
Undefined behaviour: no guarantee for the value of the data attribute!: 291474344
Undefined behaviour: no guarantee for the value of the data attribute!: 152837032
%% Cell type:markdown id: tags:
You are therefore supposed to define explicitly all the data attributes in all of your constructors. It was easy to get trumped by this in C++98/03: if you added a new data attribute and forgot to initialize it in one of your constructor, you would have undefined behaviour that is one of the worst bug to track down! (as on your machine/architecture you may have a "good" behaviour haphazardly).
Fortunately, C++ 11 introduced a mechanism I strongly recommend to provide a default value:
%% Cell type:code id: tags:
``` C++17
struct SafeClass
{
int a_ = 5; // The default value is provided here in the class declaration.
SafeClass() = default;
SafeClass(int new_value);
};
```
%% Cell type:code id: tags:
``` C++17
SafeClass::SafeClass(int new_value)
: a_(new_value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
SafeClass no_Arg;
std::cout << "If constructor doesn't change the value, default is used: " << no_Arg.a_ << std::endl;
SafeClass modified(10);
std::cout << "If constructor changes the value, choice is properly used: " << modified.a_ << std::endl;
}
```
%% Output
If constructor doesn't change the value, default is used: 5
If constructor changes the value, choice is properly used: 10
%% Cell type:markdown id: tags:
Please notice doing so doesn't prevent you to use the efficient initialization of `a_` in the cnonstructor with arguments: the values thus provided in the data attributes definitions are used only in the constructor doesn't supersede them.
%% Cell type:markdown id: tags:
In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](/notebooks/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
## Good practive: use `explicit` constructors by default
## Good practice: use `explicit` constructors by default
Let's study the following case:
%% Cell type:code id: tags:
``` C++17
struct ClassWithIntConstructor
{
ClassWithIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
void Call()
{
ClassWithIntConstructor my_object(5);
my_object = 7; // Dubious but correct: assigning an integer!
}
```
%% Cell type:code id: tags:
``` C++17
{
Call();
}
```
%% Output
Constructor called with argument 5
Constructor called with argument 7
%% Cell type:markdown id: tags:
So what happens here? In fact, the compiler implicitly convert the integer read into a constructor with an integer argument...
There are situations in which this might be deemed the right thing to do (never to my mind but I guess it depends on your programming style) but more often than not it's not what is intended and a good old compiler yell would be much preferable.
To do so, in C++ 11 you must stick the keyword **explicit** in the declaration in front of the constructor. Personally I tend to always provide it to my constructors, following the likewise advice by \cite{Meyers2015}.
%% Cell type:code id: tags:
``` C++17
struct ClassWithExplicitIntConstructor
{
explicit ClassWithExplicitIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
void CallExplicit()
{
ClassWithExplicitIntConstructor my_object(5);
my_object = 7; // COMPILATION ERROR! YAY!
}
```
%% Output
input_line_51:5:15: error: no viable overloaded '='
input_line_49:5:15: error: no viable overloaded '='
my_object = 7; // COMPILATION ERROR! YAY!
 ~~~~~~~~~ ^ ~
input_line_48:1:8: note: candidate function (the implicit copy assignment operator) not viable: no known conversion from 'int' to 'const ClassWithExplicitIntConstructor' for 1st argument
input_line_46:1:8: note: candidate function (the implicit copy assignment operator) not viable: no known conversion from 'int' to 'const ClassWithExplicitIntConstructor' for 1st argument
struct ClassWithExplicitIntConstructor
 ^
input_line_48:1:8: note: candidate function (the implicit move assignment operator) not viable: no known conversion from 'int' to 'ClassWithExplicitIntConstructor' for 1st argument
input_line_46:1:8: note: candidate function (the implicit move assignment operator) not viable: no known conversion from 'int' to 'ClassWithExplicitIntConstructor' for 1st argument
Interpreter Error:
%% Cell type:markdown id: tags:
## Destructor
%% Cell type:markdown id: tags:
The pendant of the constructor is the **destructor**, which is called when the object is terminated. Contrary to constructors, there is only one destructor for a given class, and by design it takes no parameter.
The syntax is like a constructor with no parameter with an additional `~` in front of the name.
%% Cell type:code id: tags:
``` C++17
#include <limits> // for std::numeric_limits
struct Array
{
Array(int unique_id, std::size_t array_size);
~Array(); // Destructor!
double* array_ = nullptr;
const int unique_id_ = std::numeric_limits<int>::min(); // to provide absurd value if not initialized.
// not necessarily the best choice if const wasn't
// provided: unique_id_ -- would yield 0!
};
```
%% Cell type:code id: tags:
``` C++17
Array::Array(int unique_id, std::size_t array_size)
: unique_id_(unique_id)
{
array_ = new double[array_size];
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Array::~Array()
{
std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl;
delete[] array_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Array array1(1, 5ul);
{
Array array2(2, 3ul);
{
Array array3(3, 5ul);
}
Array array4(4, 2ul);
}
}
```
%% Output
Memory for array 3 is properly freed here.
Memory for array 4 is properly freed here.
Memory for array 2 is properly freed here.
Memory for array 1 is properly freed here.
%% Cell type:markdown id: tags:
It's important to notice the ordering here: as soon as an object becomes out of scope, it is immediately destroyed; the creation order doesn't matter at all!
We will see a bit [later](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) how to take advantage of this behaviour to write programs that do not leak memory.
### Default destructor
If not specified, C++ implicitly defines a destructor with an empty body. Personally I like even in this case to make it explicit, which is done the same way as for a constructor from C++11 onward:
%% Cell type:code id: tags:
``` C++17
struct MyClass
{
MyClass() = default; // explicit default constructor
~MyClass() = default; // ezplicit default destructor
~MyClass() = default; // explicit default destructor
}
```
%% Cell type:markdown id: tags:
# References
[<a id="cit-Meyers2001" href="#call-Meyers2001">Meyers2001</a>] Scott Meyers, ``_Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library_'', 2001.
[<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018_
_This notebook is an adaptation of a lecture prepared and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% 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="#Composition" data-toc-modified-id="Composition-2.3">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:
## 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 a `Bicyle`, `Scooter` and `Car` independent classes, but to then store them in a `std::vector` would be impossible.
The idea if 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:
``` 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:code id: tags:
``` C++17
{
Square3 square(4.);
square.Print();
}
```
%% Cell type:markdown id: tags:
### 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:
``` 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.
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;
int Nwheels() const; // logically should be static if constant for all Cars2
};
```
%% Cell type:code id: tags:
``` C++17
int Bicycle2::Nwheels() const
{
return 2;
}
```
%% Cell type:code id: tags:
``` C++17
{
Bicycle2 b;
Car2 c;
Vehicle2 list[2] = { b, c };
list[0].Nwheels(); // COMPILATION ERROR
}
```
%% 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.
Defining `Nwheels()` in the base class would not work either:
%% Cell type:code id: tags:
``` C++17
class Vehicle3
{
public:
Vehicle3() = default;
int Nwheels() const;
};
class Car3 : public Vehicle3
{
public:
Car3() = default;
int Nwheels() const; // logically should be static if constant for all Cars2
};
class Bicycle3 : public Vehicle3
{
public:
Bicycle3() = default;
int Nwheels() const; // logically should be static if constant for all Cars2
};
```
%% Cell type:code id: tags:
``` C++17
int Vehicle3::Nwheels() const
{
return 0; // Stupid value, but which one should we pick up???
}
```
%% Cell type:code id: tags:
``` C++17
int Car3::Nwheels() const
{
return 4;
}
```
%% Cell type:code id: tags:
``` C++17
int Bicycle3::Nwheels() const
{
return 2;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Bicycle3 b;
Car3 c;
Vehicle3 list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // 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: `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.
### `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](/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...).
I will also introduce and explain **override** after the example,
%% Cell type:code id: tags:
``` C++17
class Vehicle4
{
public:
Vehicle4() = default;
virtual int Nwheels() const;
};
class Car4 : public Vehicle4
{
public:
Car4() = default;
virtual int Nwheels() const override;
};
class Bicycle4 : public Vehicle4
{
public:
Bicycle4() = default;
virtual int Nwheels() const override;
};
```
%% Cell type:code id: tags:
``` C++17
int Vehicle4::Nwheels() const // notice the absence of `virtual` keyword.
{
return 0; // Stupid value, but which one should we pick up???
}
```
%% Cell type:code id: tags:
``` C++17
int Car4::Nwheels() const
{
return 4;
}
```
%% Cell type:code id: tags:
``` C++17
int Bicycle4::Nwheels() const
{
return 2;
}
```
%% Cell type:markdown id: tags:
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!
* 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.
* 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
%% Cell type:code id: tags:
``` C++17
class Vehicle4_typo
{
public:
Vehicle4_typo() = default;
virtual int Nwheels() const;
};
class Car4_typo : public Vehicle4_typo
{
public:
Car4_typo() = default;
virtual int Nwhels() const; // Please notice the missing 'e'!
};
```
%% 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.
%% Cell type:code id: tags:
``` C++17
class Car4_typo_override : public Vehicle4_typo
{
public:
Car4_typo_override() = default;
virtual int Nwhels() const override; // Please notice the missing 'e', that now triggers a compilation error!
};
```
%% Cell type:markdown id: tags:
### `virtual` work only with pointers or references
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Bicycle4 b;
Car4 c;
Vehicle4 list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i].Nwheels() << std::endl; // 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>
{
Bicycle4* b = new Bicycle4;
Car4* c = new Car4;
Vehicle4* list[2] = { b, c }; // accepted even if `b` and `c` are not (only) Vehicle4 pointers!
for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl; // Better!
delete b;
delete c;
}
```
%% 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.
We're nonetheless not completely done yet:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Bicycle4* b = new Bicycle4;
Car4* c = new Car4;
Vehicle4* v = new Vehicle4;
Vehicle4* list[3] = { b, c, v };
for (auto i = 0ul; i < 3ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl;
delete b;
delete c;
delete v;
}
```
%% Cell type:markdown id: tags:
### 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.
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 Vehicle5
{
public:
Vehicle5() = default;
virtual int Nwheels() const = 0; // The only change from Vehicle4!
};
class Car5 : public Vehicle5
{
public:
Car5() = default;
virtual int Nwheels() const override;
};
class Bicycle5 : public Vehicle5
{
public:
Bicycle5() = default;
virtual int Nwheels() const override;
};
```
%% Cell type:code id: tags:
``` C++17
int Bicycle5::Nwheels() const
{
return 2;
}
```
%% Cell type:code id: tags:
``` C++17
int Car5::Nwheels() const
{
return 4;
}
```
%% Cell type:code id: tags:
``` C++17
{
Vehicle5 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>
{
Bicycle5* b = new Bicycle5;
Car5* c = new Car5;
Vehicle5* list[2] = { b, c }; // Ok!
for (auto i = 0ul; i < 2ul; ++i)
std::cout << "Nwheels = " << list[i]->Nwheels() << std::endl;
delete b;
delete c;
}
```
%% 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
class Car5WithOil : public Vehicle5
{
public:
Car5WithOil(std::string oil_type);
virtual int Nwheels() const override;
const std::string& GetOilType() const;
private:
std::string oil_type_ = "undefined";
};
```
%% Cell type:code id: tags:
``` C++17
Car5WithOil::Car5WithOil(std::string oil_type)
: Vehicle5(),
oil_type_(oil_type)
{ }
```
%% Cell type:code id: tags:
``` C++17
const std::string& Car5WithOil::GetOilType() const
{
return oil_type_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vehicle5* c = new Car5WithOil("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 `Car5WithOil`:
**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:
``` C++17
// Xeus-cling issue!
#include <iostream>
{
Car5WithOil* c = new Car5WithOil("gazole");
Bicycle5* b = new Bicycle5;
Vehicle5* list[2] = { b, c }; // Ok!
auto c_corrected = dynamic_cast<Car5WithOil*>(list[1]);
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 `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.
%% 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):
%% Cell type:code id: tags:
``` C++17
class UnderivableClass final
{ };
```
%% Cell type:code id: tags:
``` C++17
class ITryAnyway : public UnderivableClass
{ };
```
%% 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 call! 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 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;;
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; // nope by stack allocation
DerivedClass4* object2 = new DerivedClass4; // same 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;;
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();
}
```
%% Output
Hello! I'm DerivedClass5
Hello! I'm DerivedClass5
%% 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_
_This notebook is an adaptation of a lecture prepared and redacted 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 redacted 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