Showing
- 2-ObjectProgramming/4-encapsulation.ipynb 48 additions, 48 deletions2-ObjectProgramming/4-encapsulation.ipynb
- 2-ObjectProgramming/4b-hands-on.ipynb 2 additions, 2 deletions2-ObjectProgramming/4b-hands-on.ipynb
- 2-ObjectProgramming/5-static.ipynb 34 additions, 16 deletions2-ObjectProgramming/5-static.ipynb
- 2-ObjectProgramming/6-inheritance.ipynb 56 additions, 56 deletions2-ObjectProgramming/6-inheritance.ipynb
- 2-ObjectProgramming/7-polymorphism.ipynb 71 additions, 75 deletions2-ObjectProgramming/7-polymorphism.ipynb
- 2-ObjectProgramming/7b-hands-on.ipynb 5 additions, 5 deletions2-ObjectProgramming/7b-hands-on.ipynb
- 3-Operators/0-main.ipynb 5 additions, 6 deletions3-Operators/0-main.ipynb
- 3-Operators/1-Intro.ipynb 115 additions, 89 deletions3-Operators/1-Intro.ipynb
- 3-Operators/1b-hands-on.ipynb 5 additions, 6 deletions3-Operators/1b-hands-on.ipynb
- 3-Operators/2-Comparison.ipynb 150 additions, 83 deletions3-Operators/2-Comparison.ipynb
- 3-Operators/3-Stream.ipynb 29 additions, 34 deletions3-Operators/3-Stream.ipynb
- 3-Operators/3b-hands-on.ipynb 5 additions, 6 deletions3-Operators/3b-hands-on.ipynb
- 3-Operators/4-CanonicalForm.ipynb 112 additions, 46 deletions3-Operators/4-CanonicalForm.ipynb
- 3-Operators/5-Functors.ipynb 18 additions, 11 deletions3-Operators/5-Functors.ipynb
- 3-Operators/5b-hands-on.ipynb 5 additions, 6 deletions3-Operators/5b-hands-on.ipynb
- 4-Templates/0-main.ipynb 5 additions, 6 deletions4-Templates/0-main.ipynb
- 4-Templates/1-Intro.ipynb 47 additions, 38 deletions4-Templates/1-Intro.ipynb
- 4-Templates/1b-hands-on.ipynb 5 additions, 6 deletions4-Templates/1b-hands-on.ipynb
- 4-Templates/2-Specialization.ipynb 51 additions, 40 deletions4-Templates/2-Specialization.ipynb
- 4-Templates/3-Syntax.ipynb 22 additions, 35 deletions4-Templates/3-Syntax.ipynb
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Public and private | ## Public and private | ||
We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class: | We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class FirstClass | class FirstClass | ||
{ | { | ||
FirstClass(); | FirstClass(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
FirstClass::FirstClass() | FirstClass::FirstClass() | ||
{ | { | ||
std::cout << "Hello world!" << std::endl; | std::cout << "Hello world!" << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
FirstClass object; // COMPILATION ERROR! | FirstClass object; // COMPILATION ERROR! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
The compiler informs you that a **private** constructor was called. | The compiler informs you that a **private** constructor was called. | ||
In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)): | In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)): | ||
* **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook) | * **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook) | ||
* **public** means everyone may access it. | * **public** means everyone may access it. | ||
To determine which one is used, `public:` and `private:` may be added in a class declaration: | To determine which one is used, `public:` and `private:` may be added in a class declaration: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class SecondClass | class SecondClass | ||
{ | { | ||
public: | public: | ||
SecondClass(int a); | SecondClass(int a); | ||
private: | private: | ||
int a_ { -9999999 }; // stupid default value. | int a_ { -9999999 }; // stupid default value. | ||
void SetValue(int a); | void SetValue(int a); | ||
public: | public: | ||
void Print() const; | void Print() const; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
SecondClass::SecondClass(int a) | SecondClass::SecondClass(int a) | ||
{ | { | ||
SetValue(a); // Ok: a member function (here the constructor) may call private method | SetValue(a); // Ok: a member function (here the constructor) may call private method | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void SecondClass::Print() const | void SecondClass::Print() const | ||
{ | { | ||
std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute. | std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void SecondClass::SetValue(int a) | void SecondClass::SetValue(int a) | ||
{ | { | ||
a_ = a; // Ok: a member function may use private data attribute. | a_ = a; // Ok: a member function may use private data attribute. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
SecondClass object(5); | SecondClass object(5); | ||
object.Print(); // Ok: public method | object.Print(); // Ok: public method | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
SecondClass object(5); | SecondClass object(5); | ||
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method | object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least). | As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least). | ||
One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class | One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class | ||
without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library. | without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library. | ||
## Struct and class | ## Struct and class | ||
The difference in a (C++) `struct` and a `class` is in fact thin-veiled: | The difference in a (C++) `struct` and a `class` is in fact thin-veiled: | ||
* A `struct` assumes implicitly that attributes are public if nothing is specified. | * A `struct` assumes implicitly that attributes are public if nothing is specified. | ||
* A `class` assumes implicitly that attributes are private if nothing is specified. | * A `class` assumes implicitly that attributes are private if nothing is specified. | ||
I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns. | I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns. | ||
## Why encapsulation? | ## Why encapsulation? | ||
So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly: | So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
struct Rectangle | struct Rectangle | ||
{ | { | ||
Rectangle(double length, double width); | Rectangle(double length, double width); | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
void Print() const; | void Print() const; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle::Rectangle(double length, double width) | Rectangle::Rectangle(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Rectangle::Print() const | void Rectangle::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle rect(5., 4.); | Rectangle rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.length_ = 23.; | rect.length_ = 23.; | ||
rect.Print(); // Not so much... | rect.Print(); // Not so much... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Encapsulation may protect from that: | Encapsulation may protect from that: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class MoreSecureRectangle | class MoreSecureRectangle | ||
{ | { | ||
public: | public: | ||
MoreSecureRectangle(double length, double width); | MoreSecureRectangle(double length, double width); | ||
void Print() const; | void Print() const; | ||
private: | private: | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
MoreSecureRectangle::MoreSecureRectangle(double length, double width) | MoreSecureRectangle::MoreSecureRectangle(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void MoreSecureRectangle::Print() const | void MoreSecureRectangle::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
MoreSecureRectangle rect(5., 4.); | MoreSecureRectangle rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.length_ = 0.; // can't do that! | rect.length_ = 0.; // can't do that! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions: | Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle3 | class Rectangle3 | ||
{ | { | ||
public: | public: | ||
Rectangle3(double length, double widgth); | Rectangle3(double length, double widgth); | ||
void Print() const; | void Print() const; | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
private: | private: | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle3::Rectangle3(double length, double width) | Rectangle3::Rectangle3(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Rectangle3::Print() const | void Rectangle3::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle3::SetLength(double x) | void Rectangle3::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
area_ = length_ * width_; | area_ = length_ * width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle3::SetWidth(double x) | void Rectangle3::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
area_ = length_ * width_; | area_ = length_ * width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle3 rect(5., 4.); | Rectangle3 rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.SetLength(23.); | rect.SetLength(23.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It should be noticed the class above is safer, but not very good nonetheless: | It should be noticed the class above is safer, but not very good nonetheless: | ||
* The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...) | * The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...) | ||
* The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it. | * The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Good practice: data attributes should be private | ## Good practice: data attributes should be private | ||
In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object. | In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object. | ||
It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private. | It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private. | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle4 | class Rectangle4 | ||
{ | { | ||
public: | public: | ||
Rectangle4(double length, double width); | Rectangle4(double length, double width); | ||
// Mutators | // Mutators | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
// Accessors | // Accessors | ||
double GetLength() const; | double GetLength() const; | ||
double GetWidth() const; | double GetWidth() const; | ||
private: | private: | ||
double length_ { -1.e20 }; // a stupid value which at least is deterministically known... | double length_ { -1.e20 }; // a stupid value which at least is deterministically known... | ||
double width_ { -1.e20 }; | double width_ { -1.e20 }; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle4:: Rectangle4(double length, double width) | Rectangle4:: Rectangle4(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width) | width_(width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle4::SetLength(double x) | void Rectangle4::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle4::SetWidth(double x) | void Rectangle4::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle4::GetLength() const | double Rectangle4::GetLength() const | ||
{ | { | ||
return length_; | return length_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle4::GetWidth() const | double Rectangle4::GetWidth() const | ||
{ | { | ||
return width_; | return width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double ComputeArea(const Rectangle4& r) // free function | double ComputeArea(const Rectangle4& r) // free function | ||
{ | { | ||
return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here. | return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
{ | { | ||
Rectangle4 rect(4., 6.5); | Rectangle4 rect(4., 6.5); | ||
std::cout << "Area = " << ComputeArea(rect) << std::endl; | std::cout << "Area = " << ComputeArea(rect) << std::endl; | ||
rect.SetWidth(5.); | rect.SetWidth(5.); | ||
std::cout << "Area = " << ComputeArea(rect) << std::endl; | std::cout << "Area = " << ComputeArea(rect) << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary | ## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary | ||
In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this: | In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle5 | class Rectangle5 | ||
{ | { | ||
public: | public: | ||
Rectangle5(double length, double width); | Rectangle5(double length, double width); | ||
// Mutators | // Mutators | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
// Accessor | // Accessor | ||
double GetArea() const; // others accessors are not defined to limit steps to define the class | double GetArea() const; // others accessors are not defined to limit steps to define the class | ||
private: | private: | ||
// Mutator for the area | // Mutator for the area | ||
// Prototype is not bright (parameters are clearly intended to be the data attributes | // Prototype is not bright (parameters are clearly intended to be the data attributes | ||
// and a prototype with no parameters would be much wiser!) | // and a prototype with no parameters would be much wiser!) | ||
// but is useful to make my point below. | // but is useful to make my point below. | ||
void SetArea(double length, double width); | void SetArea(double length, double width); | ||
private: | private: | ||
double area_ { -1.e20 }; // a stupid value which at least is deterministic... | double area_ { -1.e20 }; // a stupid value which at least is deterministic... | ||
double length_ { -1.20 }; | double length_ { -1.20 }; | ||
double width_ { -1.20 }; | double width_ { -1.20 }; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetArea(double length, double width) | void Rectangle5::SetArea(double length, double width) | ||
{ | { | ||
area_ = width * length; | area_ = width * length; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle5:: Rectangle5(double length, double width) | Rectangle5:: Rectangle5(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width) | width_(width) | ||
{ | { | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle5::GetArea() const | double Rectangle5::GetArea() const | ||
{ | { | ||
return area_; | return area_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetLength(double x) | void Rectangle5::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetWidth(double x) | void Rectangle5::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Print5(const Rectangle5& r) | void Print5(const Rectangle5& r) | ||
{ | { | ||
std::cout << "Area is " << r.GetArea() << std::endl; | std::cout << "Area is " << r.GetArea() << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle5 rect(3., 4.); | Rectangle5 rect(3., 4.); | ||
Print5(rect); | Print5(rect); | ||
rect.SetLength(8.); | rect.SetLength(8.); | ||
Print5(rect); | Print5(rect); | ||
rect.SetWidth(2.); | rect.SetWidth(2.); | ||
Print5(rect); | Print5(rect); | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes). | It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes). | ||
A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them. | A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them. | ||
This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one... | This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one... | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Friendship | ## Friendship | ||
Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration: | Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <limits> | #include <limits> | ||
class Vector | class Vector | ||
{ | { | ||
public: | public: | ||
Vector(double x, double y, double z); | Vector(double x, double y, double z); | ||
friend double Norm(const Vector&); | friend double Norm(const Vector&); | ||
friend class PrintVector; // In C++11 `class` became optional here. | friend class PrintVector; // In C++11 `class` became optional here. | ||
friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here! | friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here! | ||
private: | private: | ||
double x_ = std::numeric_limits<double>::min(); | double x_ = std::numeric_limits<double>::min(); | ||
double y_ = std::numeric_limits<double>::min(); | double y_ = std::numeric_limits<double>::min(); | ||
double z_ = std::numeric_limits<double>::min(); | double z_ = std::numeric_limits<double>::min(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vector::Vector(double x, double y, double z) | Vector::Vector(double x, double y, double z) | ||
: x_(x), | : x_(x), | ||
y_(y), | y_(y), | ||
z_(z) | z_(z) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <cmath> | #include <cmath> | ||
double Norm(const Vector& v) | double Norm(const Vector& v) | ||
{ | { | ||
return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK! | return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK! | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class PrintVector // not the cleverest class we could write... | class PrintVector // not the cleverest class we could write... | ||
{ | { | ||
public: | public: | ||
PrintVector(const Vector& v); | PrintVector(const Vector& v); | ||
void Print() const; | void Print() const; | ||
private: | private: | ||
const Vector& vector_; | const Vector& vector_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
PrintVector::PrintVector(const Vector& v) | PrintVector::PrintVector(const Vector& v) | ||
: vector_(v) | : vector_(v) | ||
{} | {} | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void PrintVector::Print() const | void PrintVector::Print() const | ||
{ | { | ||
std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_ | std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_ | ||
<< ", " << vector_.z_ << ")." << std::endl; // OK because of friendship! | << ", " << vector_.z_ << ")." << std::endl; // OK because of friendship! | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vector v(2., 3., 5.); | Vector v(2., 3., 5.); | ||
PrintVector printer(v); | PrintVector printer(v); | ||
printer.Print(); | printer.Print(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Reset(Vector& vector) | void Reset(Vector& vector) | ||
{ | { | ||
vector.x_ = 0.; | vector.x_ = 0.; | ||
vector.y_ = 0.; | vector.y_ = 0.; | ||
vector.z_ = 0.; | vector.z_ = 0.; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
printer.Print(); | printer.Print(); | ||
Reset(v); | Reset(v); | ||
printer.Print(); | printer.Print(); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem: | Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem: | ||
* The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly. | * The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly. | ||
* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). | * The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Public and private | ## Public and private | ||
We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class: | We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class FirstClass | class FirstClass | ||
{ | { | ||
FirstClass(); | FirstClass(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
FirstClass::FirstClass() | FirstClass::FirstClass() | ||
{ | { | ||
std::cout << "Hello world!" << std::endl; | std::cout << "Hello world!" << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
FirstClass object; // COMPILATION ERROR! | FirstClass object; // COMPILATION ERROR! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
The compiler informs you that a **private** constructor was called. | The compiler informs you that a **private** constructor was called. | ||
In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)): | In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)): | ||
* **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook) | * **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook) | ||
* **public** means everyone may access it. | * **public** means everyone may access it. | ||
To determine which one is used, `public:` and `private:` may be added in a class declaration: | To determine which one is used, `public:` and `private:` may be added in a class declaration: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class SecondClass | class SecondClass | ||
{ | { | ||
public: | public: | ||
SecondClass(int a); | SecondClass(int a); | ||
private: | private: | ||
int a_ { -9999999 }; // stupid default value. | int a_ { -9999999 }; // stupid default value. | ||
void SetValue(int a); | void SetValue(int a); | ||
public: | public: | ||
void Print() const; | void Print() const; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
SecondClass::SecondClass(int a) | SecondClass::SecondClass(int a) | ||
{ | { | ||
SetValue(a); // Ok: a member function (here the constructor) may call private method | SetValue(a); // Ok: a member function (here the constructor) may call private method | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void SecondClass::Print() const | void SecondClass::Print() const | ||
{ | { | ||
std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute. | std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void SecondClass::SetValue(int a) | void SecondClass::SetValue(int a) | ||
{ | { | ||
a_ = a; // Ok: a member function may use private data attribute. | a_ = a; // Ok: a member function may use private data attribute. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
SecondClass object(5); | SecondClass object(5); | ||
object.Print(); // Ok: public method | object.Print(); // Ok: public method | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
SecondClass object(5); | SecondClass object(5); | ||
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method | object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least). | As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least). | ||
One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class | One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class | ||
without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library. | without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library. | ||
## Struct and class | ## Struct and class | ||
The difference in a (C++) `struct` and a `class` is in fact thin-veiled: | The difference in a (C++) `struct` and a `class` is in fact thin-veiled: | ||
* A `struct` assumes implicitly that attributes are public if nothing is specified. | * A `struct` assumes implicitly that attributes are public if nothing is specified. | ||
* A `class` assumes implicitly that attributes are private if nothing is specified. | * A `class` assumes implicitly that attributes are private if nothing is specified. | ||
I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns. | I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns. | ||
## Why encapsulation? | ## Why encapsulation? | ||
So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly: | So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
struct Rectangle | struct Rectangle | ||
{ | { | ||
Rectangle(double length, double width); | Rectangle(double length, double width); | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
void Print() const; | void Print() const; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle::Rectangle(double length, double width) | Rectangle::Rectangle(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Rectangle::Print() const | void Rectangle::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle rect(5., 4.); | Rectangle rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.length_ = 23.; | rect.length_ = 23.; | ||
rect.Print(); // Not so much... | rect.Print(); // Not so much... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Encapsulation may protect from that: | Encapsulation may protect from that: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class MoreSecureRectangle | class MoreSecureRectangle | ||
{ | { | ||
public: | public: | ||
MoreSecureRectangle(double length, double width); | MoreSecureRectangle(double length, double width); | ||
void Print() const; | void Print() const; | ||
private: | private: | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
MoreSecureRectangle::MoreSecureRectangle(double length, double width) | MoreSecureRectangle::MoreSecureRectangle(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void MoreSecureRectangle::Print() const | void MoreSecureRectangle::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
MoreSecureRectangle rect(5., 4.); | MoreSecureRectangle rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.length_ = 0.; // can't do that! | rect.length_ = 0.; // can't do that! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions: | Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle3 | class Rectangle3 | ||
{ | { | ||
public: | public: | ||
Rectangle3(double length, double widgth); | Rectangle3(double length, double widgth); | ||
void Print() const; | void Print() const; | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
private: | private: | ||
double length_; | double length_; | ||
double width_; | double width_; | ||
double area_; | double area_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle3::Rectangle3(double length, double width) | Rectangle3::Rectangle3(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width), | width_(width), | ||
area_(length * width) | area_(length * width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Rectangle3::Print() const | void Rectangle3::Print() const | ||
{ | { | ||
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle3::SetLength(double x) | void Rectangle3::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
area_ = length_ * width_; | area_ = length_ * width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle3::SetWidth(double x) | void Rectangle3::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
area_ = length_ * width_; | area_ = length_ * width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle3 rect(5., 4.); | Rectangle3 rect(5., 4.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
rect.SetLength(23.); | rect.SetLength(23.); | ||
rect.Print(); // OK | rect.Print(); // OK | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It should be noticed the class above is safer, but not very good nonetheless: | It should be noticed the class above is safer, but not very good nonetheless: | ||
* The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...) | * The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...) | ||
* The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it. | * The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Good practice: data attributes should be private | ## Good practice: data attributes should be private | ||
In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object. | In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object. | ||
It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private. | It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private. | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle4 | class Rectangle4 | ||
{ | { | ||
public: | public: | ||
Rectangle4(double length, double width); | Rectangle4(double length, double width); | ||
// Mutators | // Mutators | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
// Accessors | // Accessors | ||
double GetLength() const; | double GetLength() const; | ||
double GetWidth() const; | double GetWidth() const; | ||
private: | private: | ||
double length_ { -1.e20 }; // a stupid value which at least is deterministically known... | double length_ { -1.e20 }; // a stupid value which at least is deterministically known... | ||
double width_ { -1.e20 }; | double width_ { -1.e20 }; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle4:: Rectangle4(double length, double width) | Rectangle4:: Rectangle4(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width) | width_(width) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle4::SetLength(double x) | void Rectangle4::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle4::SetWidth(double x) | void Rectangle4::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle4::GetLength() const | double Rectangle4::GetLength() const | ||
{ | { | ||
return length_; | return length_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle4::GetWidth() const | double Rectangle4::GetWidth() const | ||
{ | { | ||
return width_; | return width_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double ComputeArea(const Rectangle4& r) // free function | double ComputeArea(const Rectangle4& r) // free function | ||
{ | { | ||
return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here. | return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here. | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
{ | { | ||
Rectangle4 rect(4., 6.5); | Rectangle4 rect(4., 6.5); | ||
std::cout << "Area = " << ComputeArea(rect) << std::endl; | std::cout << "Area = " << ComputeArea(rect) << std::endl; | ||
rect.SetWidth(5.); | rect.SetWidth(5.); | ||
std::cout << "Area = " << ComputeArea(rect) << std::endl; | std::cout << "Area = " << ComputeArea(rect) << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary | ## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary | ||
In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this: | In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Rectangle5 | class Rectangle5 | ||
{ | { | ||
public: | public: | ||
Rectangle5(double length, double width); | Rectangle5(double length, double width); | ||
// Mutators | // Mutators | ||
void SetLength(double x); | void SetLength(double x); | ||
void SetWidth(double x); | void SetWidth(double x); | ||
// Accessor | // Accessor | ||
double GetArea() const; // others accessors are not defined to limit steps to define the class | double GetArea() const; // others accessors are not defined to limit steps to define the class | ||
private: | private: | ||
// Mutator for the area | // Mutator for the area | ||
// Prototype is not bright (parameters are clearly intended to be the data attributes | // Prototype is not bright (parameters are clearly intended to be the data attributes | ||
// and a prototype with no parameters would be much wiser!) | // and a prototype with no parameters would be much wiser!) | ||
// but is useful to make my point below. | // but is useful to make my point below. | ||
void SetArea(double length, double width); | void SetArea(double length, double width); | ||
private: | private: | ||
double area_ { -1.e20 }; // a stupid value which at least is deterministic... | double area_ { -1.e20 }; // a stupid value which at least is deterministic... | ||
double length_ { -1.20 }; | double length_ { -1.20 }; | ||
double width_ { -1.20 }; | double width_ { -1.20 }; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetArea(double length, double width) | void Rectangle5::SetArea(double length, double width) | ||
{ | { | ||
area_ = width * length; | area_ = width * length; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle5:: Rectangle5(double length, double width) | Rectangle5:: Rectangle5(double length, double width) | ||
: length_(length), | : length_(length), | ||
width_(width) | width_(width) | ||
{ | { | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
double Rectangle5::GetArea() const | double Rectangle5::GetArea() const | ||
{ | { | ||
return area_; | return area_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetLength(double x) | void Rectangle5::SetLength(double x) | ||
{ | { | ||
length_ = x; | length_ = x; | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Rectangle5::SetWidth(double x) | void Rectangle5::SetWidth(double x) | ||
{ | { | ||
width_ = x; | width_ = x; | ||
SetArea(width_, length_); | SetArea(width_, length_); | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void Print5(const Rectangle5& r) | void Print5(const Rectangle5& r) | ||
{ | { | ||
std::cout << "Area is " << r.GetArea() << std::endl; | std::cout << "Area is " << r.GetArea() << std::endl; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Rectangle5 rect(3., 4.); | Rectangle5 rect(3., 4.); | ||
Print5(rect); | Print5(rect); | ||
rect.SetLength(8.); | rect.SetLength(8.); | ||
Print5(rect); | Print5(rect); | ||
rect.SetWidth(2.); | rect.SetWidth(2.); | ||
Print5(rect); | Print5(rect); | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes). | It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes). | ||
A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them. | A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them. | ||
This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one... | This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one... | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## Friendship | ## Friendship | ||
Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration: | Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <limits> | #include <limits> | ||
class Vector | class Vector | ||
{ | { | ||
public: | public: | ||
Vector(double x, double y, double z); | Vector(double x, double y, double z); | ||
friend double Norm(const Vector&); | friend double Norm(const Vector&); | ||
friend class PrintVector; // In C++11 `class` became optional here. | friend class PrintVector; // In C++11 `class` became optional here. | ||
friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here! | friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here! | ||
private: | private: | ||
double x_ = std::numeric_limits<double>::min(); | double x_ = std::numeric_limits<double>::min(); | ||
double y_ = std::numeric_limits<double>::min(); | double y_ = std::numeric_limits<double>::min(); | ||
double z_ = std::numeric_limits<double>::min(); | double z_ = std::numeric_limits<double>::min(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vector::Vector(double x, double y, double z) | Vector::Vector(double x, double y, double z) | ||
: x_(x), | : x_(x), | ||
y_(y), | y_(y), | ||
z_(z) | z_(z) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <cmath> | #include <cmath> | ||
double Norm(const Vector& v) | double Norm(const Vector& v) | ||
{ | { | ||
return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK! | return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK! | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class PrintVector // not the cleverest class we could write... | class PrintVector // not the cleverest class we could write... | ||
{ | { | ||
public: | public: | ||
PrintVector(const Vector& v); | PrintVector(const Vector& v); | ||
void Print() const; | void Print() const; | ||
private: | private: | ||
const Vector& vector_; | const Vector& vector_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
PrintVector::PrintVector(const Vector& v) | PrintVector::PrintVector(const Vector& v) | ||
: vector_(v) | : vector_(v) | ||
{} | {} | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
void PrintVector::Print() const | void PrintVector::Print() const | ||
{ | { | ||
std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_ | std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_ | ||
<< ", " << vector_.z_ << ")." << std::endl; // OK because of friendship! | << ", " << vector_.z_ << ")." << std::endl; // OK because of friendship! | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vector v(2., 3., 5.); | Vector v(2., 3., 5.); | ||
PrintVector printer(v); | PrintVector printer(v); | ||
printer.Print(); | printer.Print(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Reset(Vector& vector) | void Reset(Vector& vector) | ||
{ | { | ||
vector.x_ = 0.; | vector.x_ = 0.; | ||
vector.y_ = 0.; | vector.y_ = 0.; | ||
vector.z_ = 0.; | vector.z_ = 0.; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
printer.Print(); | printer.Print(); | ||
Reset(v); | Reset(v); | ||
printer.Print(); | printer.Print(); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem: | Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem: | ||
* The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly. | * The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly. | ||
* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). | * The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class** | ### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class** | ||
Make `PowerOfTwoApprox` into a class, with proper encapsulation: | Make `PowerOfTwoApprox` into a class, with proper encapsulation: | ||
* Both data attributes should be made private. | * Both data attributes should be made private. | ||
* Constant accessors will therefore be needed (non-constant ones should not be required here) | * Constant accessors will therefore be needed (non-constant ones should not be required here) | ||
Expected output is the same as previously: | Expected output is the same as previously: | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100] | [With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100] | [With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100] | [With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100] | [With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100] | [With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100] | [With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100] | [With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100] | [With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`** | ### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`** | ||
The method will take as argument only the integer coefficient. | The method will take as argument only the integer coefficient. | ||
`DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change. | `DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change. | ||
Expected output is the same as previously. | Expected output is the same as previously. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class** | ### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class** | ||
Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values. | Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values. | ||
Use two methods in this class: | Use two methods in this class: | ||
* A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument. | * A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument. | ||
* A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35). | * A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35). | ||
New main should look like: | New main should look like: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox test_display_approx; | TestDisplayPowerOfTwoApprox test_display_approx; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx.Do(nbits); | test_display_approx.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); | DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Output will be ordered differently: | Output will be ordered differently: | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | [With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | [With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | [With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | [With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | [With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | [With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | [With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | [With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class** | ### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class** | ||
Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments: | Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments: | ||
* Number of bits. | * Number of bits. | ||
* The two floating point values. | * The two floating point values. | ||
* Their integer coefficient. | * Their integer coefficient. | ||
New `main()` is: | New `main()` is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox test_display_approx; | TestDisplayPowerOfTwoApprox test_display_approx; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx.Do(nbits); | test_display_approx.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplaySumOfMultiply test_display_sum_of_multiply; | TestDisplaySumOfMultiply test_display_sum_of_multiply; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
test_display_sum_of_multiply.Do(nbits); | test_display_sum_of_multiply.Do(nbits); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 20: introduce common `PrintLine()` function for outputs** | ### **EXERCISE 20: introduce common `PrintLine()` function for outputs** | ||
Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation. | Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation. | ||
We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment... | We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment... | ||
```c++ | ```c++ | ||
// Declarations | // Declarations | ||
//! Convenient enum used in \a PrintLine(). | //! Convenient enum used in \a PrintLine(). | ||
enum class RoundToInteger { no, yes }; | enum class RoundToInteger { no, yes }; | ||
/*! | /*! | ||
* \brief Print a line with information about error. | * \brief Print a line with information about error. | ||
* | * | ||
* \param[in] maximum_error_index The error is expressed as an integer over this quantity. | * \param[in] maximum_error_index The error is expressed as an integer over this quantity. | ||
* \param[in] optional_string1 String that might appear just after the "[With N bits]:". | * \param[in] optional_string1 String that might appear just after the "[With N bits]:". | ||
* \param[in] optional_string2 String that might appear just before the "[error = ...]". | * \param[in] optional_string2 String that might appear just before the "[error = ...]". | ||
* \param[in] do_round_to_integer If yes, the exact result is approximated as an integer. | * \param[in] do_round_to_integer If yes, the exact result is approximated as an integer. | ||
*/ | */ | ||
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | ||
RoundToInteger do_round_to_integer, | RoundToInteger do_round_to_integer, | ||
std::string optional_string1 = "", std::string optional_string2 = ""); | std::string optional_string1 = "", std::string optional_string2 = ""); | ||
// Definitions | // Definitions | ||
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | ||
RoundToInteger do_round_to_integer, | RoundToInteger do_round_to_integer, | ||
std::string optional_string1, std::string optional_string2) | std::string optional_string1, std::string optional_string2) | ||
{ | { | ||
int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact); | int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact); | ||
std::cout << "[With " << Nbits << " bits]: " << optional_string1 | std::cout << "[With " << Nbits << " bits]: " << optional_string1 | ||
<< (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx | << (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx | ||
<< optional_string2 | << optional_string2 | ||
<< " [error = " << error << "/" << maximum_error_index << "]" | << " [error = " << error << "/" << maximum_error_index << "]" | ||
<< std::endl; | << std::endl; | ||
} | } | ||
``` | ``` | ||
Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged. | Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class** | ### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class** | ||
Make `PowerOfTwoApprox` into a class, with proper encapsulation: | Make `PowerOfTwoApprox` into a class, with proper encapsulation: | ||
* Both data attributes should be made private. | * Both data attributes should be made private. | ||
* Constant accessors will therefore be needed (non-constant ones should not be required here) | * Constant accessors will therefore be needed (non-constant ones should not be required here) | ||
Expected output is the same as previously: | Expected output is the same as previously: | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100] | [With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100] | [With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100] | [With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100] | [With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100] | [With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100] | [With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100] | [With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100] | [With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`** | ### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`** | ||
The method will take as argument only the integer coefficient. | The method will take as argument only the integer coefficient. | ||
`DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change. | `DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change. | ||
Expected output is the same as previously. | Expected output is the same as previously. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class** | ### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class** | ||
Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values. | Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values. | ||
Use two methods in this class: | Use two methods in this class: | ||
* A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument. | * A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument. | ||
* A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35). | * A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35). | ||
New main should look like: | New main should look like: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox test_display_approx; | TestDisplayPowerOfTwoApprox test_display_approx; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx.Do(nbits); | test_display_approx.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); | DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Output will be ordered differently: | Output will be ordered differently: | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | [With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | [With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | [With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | [With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | [With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | [With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | [With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | [With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class** | ### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class** | ||
Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments: | Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments: | ||
* Number of bits. | * Number of bits. | ||
* The two floating point values. | * The two floating point values. | ||
* Their integer coefficient. | * Their integer coefficient. | ||
New `main()` is: | New `main()` is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox test_display_approx; | TestDisplayPowerOfTwoApprox test_display_approx; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx.Do(nbits); | test_display_approx.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplaySumOfMultiply test_display_sum_of_multiply; | TestDisplaySumOfMultiply test_display_sum_of_multiply; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
test_display_sum_of_multiply.Do(nbits); | test_display_sum_of_multiply.Do(nbits); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 20: introduce common `PrintLine()` function for outputs** | ### **EXERCISE 20: introduce common `PrintLine()` function for outputs** | ||
Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation. | Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation. | ||
We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment... | We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment... | ||
```c++ | ```c++ | ||
// Declarations | // Declarations | ||
//! Convenient enum used in \a PrintLine(). | //! Convenient enum used in \a PrintLine(). | ||
enum class RoundToInteger { no, yes }; | enum class RoundToInteger { no, yes }; | ||
/*! | /*! | ||
* \brief Print a line with information about error. | * \brief Print a line with information about error. | ||
* | * | ||
* \param[in] maximum_error_index The error is expressed as an integer over this quantity. | * \param[in] maximum_error_index The error is expressed as an integer over this quantity. | ||
* \param[in] optional_string1 String that might appear just after the "[With N bits]:". | * \param[in] optional_string1 String that might appear just after the "[With N bits]:". | ||
* \param[in] optional_string2 String that might appear just before the "[error = ...]". | * \param[in] optional_string2 String that might appear just before the "[error = ...]". | ||
* \param[in] do_round_to_integer If yes, the exact result is approximated as an integer. | * \param[in] do_round_to_integer If yes, the exact result is approximated as an integer. | ||
*/ | */ | ||
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | ||
RoundToInteger do_round_to_integer, | RoundToInteger do_round_to_integer, | ||
std::string optional_string1 = "", std::string optional_string2 = ""); | std::string optional_string1 = "", std::string optional_string2 = ""); | ||
// Definitions | // Definitions | ||
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, | ||
RoundToInteger do_round_to_integer, | RoundToInteger do_round_to_integer, | ||
std::string optional_string1, std::string optional_string2) | std::string optional_string1, std::string optional_string2) | ||
{ | { | ||
int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact); | int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact); | ||
std::cout << "[With " << Nbits << " bits]: " << optional_string1 | std::cout << "[With " << Nbits << " bits]: " << optional_string1 | ||
<< (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx | << (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx | ||
<< optional_string2 | << optional_string2 | ||
<< " [error = " << error << "/" << maximum_error_index << "]" | << " [error = " << error << "/" << maximum_error_index << "]" | ||
<< std::endl; | << std::endl; | ||
} | } | ||
``` | ``` | ||
Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged. | Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb) | ||
%% 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 array (or even better `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 array (or even better `std::vector`) would be impossible. | ||
The idea of inheritance is to provide a **base class** from which our classes are derived: | 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++ | ||
#include <iostream> | #include <iostream> | ||
enum class motor_type { none, thermic, electric }; | enum class motor_type { none, thermic, electric }; | ||
class Vehicle | class Vehicle | ||
{ | { | ||
public: | public: | ||
Vehicle(motor_type type); | Vehicle(motor_type type); | ||
void Print() const { std::cout << "I'm a Vehicle!" << std::endl; } | void Print() const { std::cout << "I'm a Vehicle!" << std::endl; } | ||
private: | private: | ||
motor_type type_; | motor_type type_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vehicle::Vehicle(motor_type type) | Vehicle::Vehicle(motor_type type) | ||
: type_(type) | : type_(type) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricBicycle : public Vehicle | class ElectricBicycle : public Vehicle | ||
{ | { | ||
public: | public: | ||
ElectricBicycle(); | ElectricBicycle(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ThermicCar : public Vehicle | class ThermicCar : public Vehicle | ||
{ | { | ||
public: | public: | ||
ThermicCar(); | ThermicCar(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ElectricBicycle::ElectricBicycle() | ElectricBicycle::ElectricBicycle() | ||
: Vehicle(motor_type::electric) | : Vehicle(motor_type::electric) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ThermicCar::ThermicCar() | ThermicCar::ThermicCar() | ||
: Vehicle(motor_type::thermic) | : Vehicle(motor_type::thermic) | ||
{ } | { } | ||
``` | ``` | ||
%% 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 parts 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 parts of the derived class, then the base class (in most cases you don't have to care about that). | ||
### Multiple layer of inheritance | ### Multiple layer of inheritance | ||
A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)). | A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)). | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricVehicle : public Vehicle | class ElectricVehicle : public Vehicle | ||
{ | { | ||
public: | public: | ||
ElectricVehicle(); | ElectricVehicle(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ElectricVehicle::ElectricVehicle() | ElectricVehicle::ElectricVehicle() | ||
: Vehicle(motor_type::electric) | : Vehicle(motor_type::electric) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricCar : public ElectricVehicle | class ElectricCar : public ElectricVehicle | ||
{ | { | ||
public: | public: | ||
ElectricCar() = default; | ElectricCar() = default; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### Multiple inheritance | ### Multiple inheritance | ||
It is also possible for a class to inherit from several parents: | It is also possible for a class to inherit from several parents: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class BlueObjects | class BlueObjects | ||
{ | { | ||
public: | public: | ||
BlueObjects() = default; | BlueObjects() = default; | ||
int Print() | int Print() | ||
{ | { | ||
std::cout << "I'm blue!" << std::endl; | std::cout << "I'm blue!" << std::endl; | ||
return 42; | return 42; | ||
} | } | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class BlueVehicle : public Vehicle, public BlueObjects | class BlueVehicle : public Vehicle, public BlueObjects | ||
{ | { | ||
public: | public: | ||
BlueVehicle(motor_type type); | BlueVehicle(motor_type type); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
BlueVehicle::BlueVehicle(motor_type type) | BlueVehicle::BlueVehicle(motor_type type) | ||
: Vehicle(type), // mandatory call of the non-default constructor | : Vehicle(type), // mandatory call of the non-default constructor | ||
BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly | BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
BlueVehicle blue_bike(motor_type::none); | BlueVehicle blue_bike(motor_type::none); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
**Beware:** there might be ambiguity between some methods names, regardless of prototype: | **Beware:** there might be ambiguity between some methods names, regardless of prototype: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ? | blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ? | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place: | It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.BlueObjects::Print(); | blue_bike.BlueObjects::Print(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.Vehicle::Print(); | blue_bike.Vehicle::Print(); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### Diamond inheritance | ### Diamond inheritance | ||
In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`: | In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`: | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
<img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/> | <img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/> | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
I really advise you to avoid this kind of things whenever possible, but if you _really_ need this: | I really advise you to avoid this kind of things whenever possible, but if you _really_ need this: | ||
- First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!) | - First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!) | ||
- Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line). | - Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line). | ||
%% Cell type:markdown id: tags: | %% 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++ | ||
#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 } ; // Stupid value that would play havoc if not properly initialized | double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized | ||
// - std::optional (C++17) would probably a better choice. | // - std::optional (C++17) would probably a better choice. | ||
double length_ { -1.e20 } ; | double length_ { -1.e20 } ; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
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++ | ||
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++ | ||
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++ | ||
#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++ | ||
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++ | ||
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++ | ||
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++ | ||
{ | { | ||
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 [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)). | 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 [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)). | ||
Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best. | Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best. | ||
The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). | The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). | ||
We will now "fix" our `Square` problem with two different idioms: private inheritance and composition. | We will now "fix" our `Square` problem with two different idioms: 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 for in fact is **private** inheritance, in which all the inherited attributes are considered private: | What you might look for in fact is **private** inheritance, in which all the inherited attributes are considered private: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
{ | { | ||
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++ | ||
{ | { | ||
Square2 square(4.); | Square2 square(4.); | ||
ModifyRectangle(square); // COMPILATION ERROR! | ModifyRectangle(square); // COMPILATION ERROR! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept. | So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept. | ||
And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy): | And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy): | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
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++ | ||
{ | { | ||
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++ | ||
class Square4 | class Square4 | ||
{ | { | ||
public: | public: | ||
Square4(double side_dimension); | Square4(double side_dimension); | ||
~Square4() = default; | ~Square4() = default; | ||
void Print() const; | void Print() const; | ||
void SetSideDimension(double x); | void SetSideDimension(double x); | ||
private: | private: | ||
const Rectangle& GetRectangle() const; | const Rectangle& GetRectangle() const; | ||
Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive! | Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive! | ||
private: | private: | ||
Rectangle rectangle_; | Rectangle rectangle_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
const Rectangle& Square4::GetRectangle() const | const Rectangle& Square4::GetRectangle() const | ||
{ | { | ||
return rectangle_; | return rectangle_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle& Square4::GetNonConstantRectangle() | Rectangle& Square4::GetNonConstantRectangle() | ||
{ | { | ||
return rectangle_; | return rectangle_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Square4::Square4(double side_dimension) | Square4::Square4(double side_dimension) | ||
: rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class | : rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Square4::SetSideDimension(double x) | void Square4::SetSideDimension(double x) | ||
{ | { | ||
auto& rectangle = GetNonConstantRectangle(); | auto& rectangle = GetNonConstantRectangle(); | ||
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++ | ||
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++ | ||
{ | { | ||
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: | ||
### Private inheritance vs composition | ### Private inheritance vs composition | ||
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 verbose: 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 if you need most of the API of the base class, private inheritance is less work. | - But composition is more verbose: 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 if you need most of the API of the base class, private inheritance is less work. | ||
As indicated before, I tend to use composition more, but it's still nice to know both are available. | As indicated before, I tend to use composition more, but it's still nice 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 the status of public members: | Our discussion about public and private inheritance has highlighted the importance of the propagation of the 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++ | ||
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++ | ||
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++ | ||
DerivedClass::DerivedClass() | DerivedClass::DerivedClass() | ||
{ | { | ||
DoNothing(); // COMPILATION ERROR | DoNothing(); // COMPILATION ERROR | ||
} | } | ||
``` | ``` | ||
%% 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++ | ||
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++ | ||
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++ | ||
DerivedClass2::DerivedClass2() | DerivedClass2::DerivedClass2() | ||
{ | { | ||
DoNothing(); // Ok | DoNothing(); // Ok | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
auto object = DerivedClass2(); | auto object = DerivedClass2(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
object.DoNothing(); // COMPILATION ERROR! | object.DoNothing(); // COMPILATION ERROR! | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`. | Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`. | ||
%% 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) summed 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) summed this up with this nice cheatsheet: | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
") | ") | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## **[WARNING]** Be wary of the slicing effect | ## **[WARNING]** Be wary of the slicing effect | ||
A note of caution about public inheritance: | A note of caution about public inheritance: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Base { }; | class Base { }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
class Derived : public Base | class Derived : public Base | ||
{ | { | ||
public: | public: | ||
Derived(int val) | Derived(int val) | ||
: value_ { val } | : value_ { val } | ||
{ } | { } | ||
private: | private: | ||
int value_; | int value_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Base base; | Base base; | ||
Derived derived { 5 }; | Derived derived { 5 }; | ||
base = derived; // dangerous line... | base = derived; // dangerous line... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object... | This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object... | ||
This is the so-called **slicing effect**. | This is the so-called **slicing effect**. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Here it may seem obvious, but it might be insidious if you use functions with pass-by-value: | Here it may seem obvious, but it might be insidious if you use functions with pass-by-value: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void ComputePoorlyStuff(Base base) | void ComputePoorlyStuff(Base base) | ||
{ | { | ||
// ... | // ... | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Derived derived { 5 }; | Derived derived { 5 }; | ||
ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned! | ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function! | you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function! | ||
The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy! | The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy! | ||
Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm: | Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void ComputeStuff(const Base& base) // ok, no slicing effect! | void ComputeStuff(const Base& base) // ok, no slicing effect! | ||
{ | { | ||
// ... | // ... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing). | If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb) | ||
%% 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 array (or even better `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 array (or even better `std::vector`) would be impossible. | ||
The idea of inheritance is to provide a **base class** from which our classes are derived: | 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++ | ||
#include <iostream> | #include <iostream> | ||
enum class motor_type { none, thermic, electric }; | enum class motor_type { none, thermic, electric }; | ||
class Vehicle | class Vehicle | ||
{ | { | ||
public: | public: | ||
Vehicle(motor_type type); | Vehicle(motor_type type); | ||
void Print() const { std::cout << "I'm a Vehicle!" << std::endl; } | void Print() const { std::cout << "I'm a Vehicle!" << std::endl; } | ||
private: | private: | ||
motor_type type_; | motor_type type_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Vehicle::Vehicle(motor_type type) | Vehicle::Vehicle(motor_type type) | ||
: type_(type) | : type_(type) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricBicycle : public Vehicle | class ElectricBicycle : public Vehicle | ||
{ | { | ||
public: | public: | ||
ElectricBicycle(); | ElectricBicycle(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ThermicCar : public Vehicle | class ThermicCar : public Vehicle | ||
{ | { | ||
public: | public: | ||
ThermicCar(); | ThermicCar(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ElectricBicycle::ElectricBicycle() | ElectricBicycle::ElectricBicycle() | ||
: Vehicle(motor_type::electric) | : Vehicle(motor_type::electric) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ThermicCar::ThermicCar() | ThermicCar::ThermicCar() | ||
: Vehicle(motor_type::thermic) | : Vehicle(motor_type::thermic) | ||
{ } | { } | ||
``` | ``` | ||
%% 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 parts 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 parts of the derived class, then the base class (in most cases you don't have to care about that). | ||
### Multiple layer of inheritance | ### Multiple layer of inheritance | ||
A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)). | A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)). | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricVehicle : public Vehicle | class ElectricVehicle : public Vehicle | ||
{ | { | ||
public: | public: | ||
ElectricVehicle(); | ElectricVehicle(); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
ElectricVehicle::ElectricVehicle() | ElectricVehicle::ElectricVehicle() | ||
: Vehicle(motor_type::electric) | : Vehicle(motor_type::electric) | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class ElectricCar : public ElectricVehicle | class ElectricCar : public ElectricVehicle | ||
{ | { | ||
public: | public: | ||
ElectricCar() = default; | ElectricCar() = default; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### Multiple inheritance | ### Multiple inheritance | ||
It is also possible for a class to inherit from several parents: | It is also possible for a class to inherit from several parents: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class BlueObjects | class BlueObjects | ||
{ | { | ||
public: | public: | ||
BlueObjects() = default; | BlueObjects() = default; | ||
int Print() | int Print() | ||
{ | { | ||
std::cout << "I'm blue!" << std::endl; | std::cout << "I'm blue!" << std::endl; | ||
return 42; | return 42; | ||
} | } | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class BlueVehicle : public Vehicle, public BlueObjects | class BlueVehicle : public Vehicle, public BlueObjects | ||
{ | { | ||
public: | public: | ||
BlueVehicle(motor_type type); | BlueVehicle(motor_type type); | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
BlueVehicle::BlueVehicle(motor_type type) | BlueVehicle::BlueVehicle(motor_type type) | ||
: Vehicle(type), // mandatory call of the non-default constructor | : Vehicle(type), // mandatory call of the non-default constructor | ||
BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly | BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
BlueVehicle blue_bike(motor_type::none); | BlueVehicle blue_bike(motor_type::none); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
**Beware:** there might be ambiguity between some methods names, regardless of prototype: | **Beware:** there might be ambiguity between some methods names, regardless of prototype: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ? | blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ? | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place: | It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.BlueObjects::Print(); | blue_bike.BlueObjects::Print(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
blue_bike.Vehicle::Print(); | blue_bike.Vehicle::Print(); | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### Diamond inheritance | ### Diamond inheritance | ||
In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`: | In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`: | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
<img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/> | <img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/> | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
I really advise you to avoid this kind of things whenever possible, but if you _really_ need this: | I really advise you to avoid this kind of things whenever possible, but if you _really_ need this: | ||
- First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!) | - First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!) | ||
- Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line). | - Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line). | ||
%% Cell type:markdown id: tags: | %% 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++ | ||
#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 } ; // Stupid value that would play havoc if not properly initialized | double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized | ||
// - std::optional (C++17) would probably a better choice. | // - std::optional (C++17) would probably a better choice. | ||
double length_ { -1.e20 } ; | double length_ { -1.e20 } ; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
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++ | ||
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++ | ||
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++ | ||
#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++ | ||
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++ | ||
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++ | ||
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++ | ||
{ | { | ||
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 [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)). | 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 [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)). | ||
Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best. | Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best. | ||
The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). | The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). | ||
We will now "fix" our `Square` problem with two different idioms: private inheritance and composition. | We will now "fix" our `Square` problem with two different idioms: 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 for in fact is **private** inheritance, in which all the inherited attributes are considered private: | What you might look for in fact is **private** inheritance, in which all the inherited attributes are considered private: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
{ | { | ||
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++ | ||
{ | { | ||
Square2 square(4.); | Square2 square(4.); | ||
ModifyRectangle(square); // COMPILATION ERROR! | ModifyRectangle(square); // COMPILATION ERROR! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept. | So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept. | ||
And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy): | And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy): | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
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++ | ||
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++ | ||
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++ | ||
{ | { | ||
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++ | ||
class Square4 | class Square4 | ||
{ | { | ||
public: | public: | ||
Square4(double side_dimension); | Square4(double side_dimension); | ||
~Square4() = default; | ~Square4() = default; | ||
void Print() const; | void Print() const; | ||
void SetSideDimension(double x); | void SetSideDimension(double x); | ||
private: | private: | ||
const Rectangle& GetRectangle() const; | const Rectangle& GetRectangle() const; | ||
Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive! | Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive! | ||
private: | private: | ||
Rectangle rectangle_; | Rectangle rectangle_; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
const Rectangle& Square4::GetRectangle() const | const Rectangle& Square4::GetRectangle() const | ||
{ | { | ||
return rectangle_; | return rectangle_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Rectangle& Square4::GetNonConstantRectangle() | Rectangle& Square4::GetNonConstantRectangle() | ||
{ | { | ||
return rectangle_; | return rectangle_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
Square4::Square4(double side_dimension) | Square4::Square4(double side_dimension) | ||
: rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class | : rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class | ||
{ } | { } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void Square4::SetSideDimension(double x) | void Square4::SetSideDimension(double x) | ||
{ | { | ||
auto& rectangle = GetNonConstantRectangle(); | auto& rectangle = GetNonConstantRectangle(); | ||
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++ | ||
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++ | ||
{ | { | ||
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: | ||
### Private inheritance vs composition | ### Private inheritance vs composition | ||
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 verbose: 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 if you need most of the API of the base class, private inheritance is less work. | - But composition is more verbose: 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 if you need most of the API of the base class, private inheritance is less work. | ||
As indicated before, I tend to use composition more, but it's still nice to know both are available. | As indicated before, I tend to use composition more, but it's still nice 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 the status of public members: | Our discussion about public and private inheritance has highlighted the importance of the propagation of the 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++ | ||
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++ | ||
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++ | ||
DerivedClass::DerivedClass() | DerivedClass::DerivedClass() | ||
{ | { | ||
DoNothing(); // COMPILATION ERROR | DoNothing(); // COMPILATION ERROR | ||
} | } | ||
``` | ``` | ||
%% 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++ | ||
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++ | ||
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++ | ||
DerivedClass2::DerivedClass2() | DerivedClass2::DerivedClass2() | ||
{ | { | ||
DoNothing(); // Ok | DoNothing(); // Ok | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
auto object = DerivedClass2(); | auto object = DerivedClass2(); | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
object.DoNothing(); // COMPILATION ERROR! | object.DoNothing(); // COMPILATION ERROR! | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`. | Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`. | ||
%% 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) summed 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) summed this up with this nice cheatsheet: | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
") | ") | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
## **[WARNING]** Be wary of the slicing effect | ## **[WARNING]** Be wary of the slicing effect | ||
A note of caution about public inheritance: | A note of caution about public inheritance: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class Base { }; | class Base { }; | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
#include <iostream> | #include <iostream> | ||
class Derived : public Base | class Derived : public Base | ||
{ | { | ||
public: | public: | ||
Derived(int val) | Derived(int val) | ||
: value_ { val } | : value_ { val } | ||
{ } | { } | ||
private: | private: | ||
int value_; | int value_; | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Base base; | Base base; | ||
Derived derived { 5 }; | Derived derived { 5 }; | ||
base = derived; // dangerous line... | base = derived; // dangerous line... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object... | This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object... | ||
This is the so-called **slicing effect**. | This is the so-called **slicing effect**. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
Here it may seem obvious, but it might be insidious if you use functions with pass-by-value: | Here it may seem obvious, but it might be insidious if you use functions with pass-by-value: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void ComputePoorlyStuff(Base base) | void ComputePoorlyStuff(Base base) | ||
{ | { | ||
// ... | // ... | ||
} | } | ||
``` | ``` | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
{ | { | ||
Derived derived { 5 }; | Derived derived { 5 }; | ||
ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned! | ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned! | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function! | you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function! | ||
The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy! | The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy! | ||
Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm: | Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
void ComputeStuff(const Base& base) // ok, no slicing effect! | void ComputeStuff(const Base& base) // ok, no slicing effect! | ||
{ | { | ||
// ... | // ... | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing). | If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 21: base class `TestDisplay`** | ### **EXERCISE 21: base class `TestDisplay`** | ||
Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly. | Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly. | ||
This class: | This class: | ||
* Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes) | * Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes) | ||
* Should define the `RoundToInteger` enum (as protected) | * Should define the `RoundToInteger` enum (as protected) | ||
* Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise. | * Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise. | ||
The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones. | The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones. | ||
**Note:** If you're using clang with warnings activated, you may get this warning: | **Note:** If you're using clang with warnings activated, you may get this warning: | ||
```shell | ```shell | ||
warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] | warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] | ||
``` | ``` | ||
To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword. | To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword. | ||
In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called. | In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`** | ### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`** | ||
We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35. | We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35. | ||
To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`. | To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`. | ||
Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method. | Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method. | ||
**Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation. | **Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation. | ||
The `main()` to use: | The `main()` to use: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox065 test_display_approx065(100); | TestDisplayPowerOfTwoApprox065 test_display_approx065(100); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx065.Do(nbits); | test_display_approx065.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplayPowerOfTwoApprox035 test_display_approx035(100); | TestDisplayPowerOfTwoApprox035 test_display_approx035(100); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx035.Do(nbits); | test_display_approx035.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplaySumOfMultiply test_display_sum_of_multiply(1000); | TestDisplaySumOfMultiply test_display_sum_of_multiply(1000); | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
test_display_sum_of_multiply.Do(nbits); | test_display_sum_of_multiply.Do(nbits); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
_Expected output:_ | _Expected output:_ | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 23: Toward a `TestDisplayContainer` class** | ### **EXERCISE 23: Toward a `TestDisplayContainer` class** | ||
We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method. | We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method. | ||
The declaration of the class should look like: | The declaration of the class should look like: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class TestDisplayContainer | class TestDisplayContainer | ||
{ | { | ||
public: | public: | ||
//! Maximum number of objects that might be stored (to avoid magic number) | //! Maximum number of objects that might be stored (to avoid magic number) | ||
static constexpr std::size_t MAX_ELTS { 3ul }; | static constexpr std::size_t MAX_ELTS { 3ul }; | ||
//! Add a new test_display_register. | //! Add a new test_display_register. | ||
//! At each call, the item to be registered is put at the first available position and internal current_position_ | //! At each call, the item to be registered is put at the first available position and internal current_position_ | ||
//! is incremented. If the end-user attempts to register more than three items, the Error() function is called. | //! is incremented. If the end-user attempts to register more than three items, the Error() function is called. | ||
void Register(TestDisplay* test_display); | void Register(TestDisplay* test_display); | ||
//! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen. | //! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen. | ||
void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const; | void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const; | ||
private: | private: | ||
//! List of all known `TestDisplay` objects. | //! List of all known `TestDisplay` objects. | ||
TestDisplay* list_[MAX_ELTS]; | TestDisplay* list_[MAX_ELTS]; | ||
//! Index to place the next register object. If '3', no more object may be registered. | //! Index to place the next register object. If '3', no more object may be registered. | ||
std::size_t current_position_ {}; | std::size_t current_position_ {}; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file): | You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file): | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
//! Function for error handling. We will see later how to fulfill the same functionality more properly. | //! Function for error handling. We will see later how to fulfill the same functionality more properly. | ||
[[noreturn]] void Error(std::string explanation) | [[noreturn]] void Error(std::string explanation) | ||
{ | { | ||
std::cout << "ERROR: " << explanation << std::endl; | std::cout << "ERROR: " << explanation << std::endl; | ||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
The `main()` to use is: | The `main()` to use is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayContainer container; | TestDisplayContainer container; | ||
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution | container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution | ||
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution | container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution | ||
container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution | container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution | ||
container.Do(4, 16, 4); | container.Do(4, 16, 4); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
_Expected result:_ | _Expected result:_ | ||
``` | ``` | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000] | [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000] | [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000] | ||
[With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000] | [With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000] | ||
[With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000] | [With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000] | [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000] | [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000] | ||
[With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000] | [With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000] | ||
[With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000] | [With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000] | ||
[With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | [With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | ||
[With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | [With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
**Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings: | **Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings: | ||
```shell | ```shell | ||
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage] | /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage] | ||
458 | list_[current_position_] = test_display; | 458 | list_[current_position_] = test_display; | ||
| ^~~~~ | | ^~~~~ | ||
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage] | /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage] | ||
468 | list_[i]->Do(nbits); | 468 | list_[i]->Do(nbits); | ||
``` | ``` | ||
The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority). | The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 24: dynamic allocation of array** | ### **EXERCISE 24: dynamic allocation of array** | ||
Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be: | Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be: | ||
``` | ``` | ||
TestDisplay** list_; | TestDisplay** list_; | ||
``` | ``` | ||
meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit). | meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit). | ||
Constructor must now: | Constructor must now: | ||
* Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim). | * Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim). | ||
* Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction). | * Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction). | ||
* Set each element to `nullptr`. | * Set each element to `nullptr`. | ||
Destructor must of course take care of deallocating properly the memory. | Destructor must of course take care of deallocating properly the memory. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function** | ### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function** | ||
We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop). | We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop). | ||
Replace the `TestDisplayContainer::Do()` method by a free function with signature: | Replace the `TestDisplayContainer::Do()` method by a free function with signature: | ||
```c++ | ```c++ | ||
void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container) | void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container) | ||
``` | ``` | ||
To do so, you will need to add several methods to `TestDisplayContainer`: | To do so, you will need to add several methods to `TestDisplayContainer`: | ||
- A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements. | - A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements. | ||
- A method to access the `i`-th element stored in the table. Signature might be: | - A method to access the `i`-th element stored in the table. Signature might be: | ||
```c++ | ```c++ | ||
const TestDisplay& GetElement(std::size_t i) const | const TestDisplay& GetElement(std::size_t i) const | ||
``` | ``` | ||
(but others are also possible - you may prefer to return a pointer rather than a reference). | (but others are also possible - you may prefer to return a pointer rather than a reference). | ||
New `main()` is: | New `main()` is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayContainer container(3ul); | TestDisplayContainer container(3ul); | ||
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); | container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); | ||
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); | container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); | ||
container.Register(new TestDisplaySumOfMultiply(1000000)); | container.Register(new TestDisplaySumOfMultiply(1000000)); | ||
Loop(4, 16, 4, container); | Loop(4, 16, 4, container); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb) | # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 21: base class `TestDisplay`** | ### **EXERCISE 21: base class `TestDisplay`** | ||
Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly. | Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly. | ||
This class: | This class: | ||
* Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes) | * Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes) | ||
* Should define the `RoundToInteger` enum (as protected) | * Should define the `RoundToInteger` enum (as protected) | ||
* Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise. | * Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise. | ||
The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones. | The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones. | ||
**Note:** If you're using clang with warnings activated, you may get this warning: | **Note:** If you're using clang with warnings activated, you may get this warning: | ||
```shell | ```shell | ||
warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] | warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] | ||
``` | ``` | ||
To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword. | To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword. | ||
In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called. | In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`** | ### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`** | ||
We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35. | We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35. | ||
To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`. | To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`. | ||
Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method. | Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method. | ||
**Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation. | **Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation. | ||
The `main()` to use: | The `main()` to use: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayPowerOfTwoApprox065 test_display_approx065(100); | TestDisplayPowerOfTwoApprox065 test_display_approx065(100); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx065.Do(nbits); | test_display_approx065.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplayPowerOfTwoApprox035 test_display_approx035(100); | TestDisplayPowerOfTwoApprox035 test_display_approx035(100); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | for (int nbits = 2; nbits <= 8; nbits += 2) | ||
test_display_approx035.Do(nbits); | test_display_approx035.Do(nbits); | ||
std::cout << std::endl; | std::cout << std::endl; | ||
TestDisplaySumOfMultiply test_display_sum_of_multiply(1000); | TestDisplaySumOfMultiply test_display_sum_of_multiply(1000); | ||
for (int nbits = 1; nbits <= 8; ++nbits) | for (int nbits = 1; nbits <= 8; ++nbits) | ||
test_display_sum_of_multiply.Do(nbits); | test_display_sum_of_multiply.Do(nbits); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
_Expected output:_ | _Expected output:_ | ||
``` | ``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 23: Toward a `TestDisplayContainer` class** | ### **EXERCISE 23: Toward a `TestDisplayContainer` class** | ||
We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method. | We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method. | ||
The declaration of the class should look like: | The declaration of the class should look like: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
class TestDisplayContainer | class TestDisplayContainer | ||
{ | { | ||
public: | public: | ||
//! Maximum number of objects that might be stored (to avoid magic number) | //! Maximum number of objects that might be stored (to avoid magic number) | ||
static constexpr std::size_t MAX_ELTS { 3ul }; | static constexpr std::size_t MAX_ELTS { 3ul }; | ||
//! Add a new test_display_register. | //! Add a new test_display_register. | ||
//! At each call, the item to be registered is put at the first available position and internal current_position_ | //! At each call, the item to be registered is put at the first available position and internal current_position_ | ||
//! is incremented. If the end-user attempts to register more than three items, the Error() function is called. | //! is incremented. If the end-user attempts to register more than three items, the Error() function is called. | ||
void Register(TestDisplay* test_display); | void Register(TestDisplay* test_display); | ||
//! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen. | //! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen. | ||
void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const; | void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const; | ||
private: | private: | ||
//! List of all known `TestDisplay` objects. | //! List of all known `TestDisplay` objects. | ||
TestDisplay* list_[MAX_ELTS]; | TestDisplay* list_[MAX_ELTS]; | ||
//! Index to place the next register object. If '3', no more object may be registered. | //! Index to place the next register object. If '3', no more object may be registered. | ||
std::size_t current_position_ {}; | std::size_t current_position_ {}; | ||
}; | }; | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file): | You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file): | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
//! Function for error handling. We will see later how to fulfill the same functionality more properly. | //! Function for error handling. We will see later how to fulfill the same functionality more properly. | ||
[[noreturn]] void Error(std::string explanation) | [[noreturn]] void Error(std::string explanation) | ||
{ | { | ||
std::cout << "ERROR: " << explanation << std::endl; | std::cout << "ERROR: " << explanation << std::endl; | ||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
The `main()` to use is: | The `main()` to use is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayContainer container; | TestDisplayContainer container; | ||
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution | container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution | ||
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution | container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution | ||
container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution | container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution | ||
container.Do(4, 16, 4); | container.Do(4, 16, 4); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
_Expected result:_ | _Expected result:_ | ||
``` | ``` | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000] | [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000] | [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000] | ||
[With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000] | [With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000] | ||
[With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000] | [With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000] | [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000] | [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000] | ||
[With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000] | [With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000] | ||
[With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000] | [With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000] | [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000] | [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000] | ||
[With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | [With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | ||
[With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | [With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
**Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings: | **Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings: | ||
```shell | ```shell | ||
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage] | /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage] | ||
458 | list_[current_position_] = test_display; | 458 | list_[current_position_] = test_display; | ||
| ^~~~~ | | ^~~~~ | ||
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage] | /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage] | ||
468 | list_[i]->Do(nbits); | 468 | list_[i]->Do(nbits); | ||
``` | ``` | ||
The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority). | The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority). | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 24: dynamic allocation of array** | ### **EXERCISE 24: dynamic allocation of array** | ||
Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be: | Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be: | ||
``` | ``` | ||
TestDisplay** list_; | TestDisplay** list_; | ||
``` | ``` | ||
meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit). | meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit). | ||
Constructor must now: | Constructor must now: | ||
* Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim). | * Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim). | ||
* Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction). | * Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction). | ||
* Set each element to `nullptr`. | * Set each element to `nullptr`. | ||
Destructor must of course take care of deallocating properly the memory. | Destructor must of course take care of deallocating properly the memory. | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function** | ### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function** | ||
We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop). | We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop). | ||
Replace the `TestDisplayContainer::Do()` method by a free function with signature: | Replace the `TestDisplayContainer::Do()` method by a free function with signature: | ||
```c++ | ```c++ | ||
void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container) | void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container) | ||
``` | ``` | ||
To do so, you will need to add several methods to `TestDisplayContainer`: | To do so, you will need to add several methods to `TestDisplayContainer`: | ||
- A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements. | - A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements. | ||
- A method to access the `i`-th element stored in the table. Signature might be: | - A method to access the `i`-th element stored in the table. Signature might be: | ||
```c++ | ```c++ | ||
const TestDisplay& GetElement(std::size_t i) const | const TestDisplay& GetElement(std::size_t i) const | ||
``` | ``` | ||
(but others are also possible - you may prefer to return a pointer rather than a reference). | (but others are also possible - you may prefer to return a pointer rather than a reference). | ||
New `main()` is: | New `main()` is: | ||
%% Cell type:code id: tags: | %% Cell type:code id: tags: | ||
``` C++17 | ``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | { | ||
TestDisplayContainer container(3ul); | TestDisplayContainer container(3ul); | ||
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); | container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); | ||
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); | container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); | ||
container.Register(new TestDisplaySumOfMultiply(1000000)); | container.Register(new TestDisplaySumOfMultiply(1000000)); | ||
Loop(4, 16, 4, container); | Loop(4, 16, 4, container); | ||
return EXIT_SUCCESS; | return EXIT_SUCCESS; | ||
} | } | ||
``` | ``` | ||
%% Cell type:markdown id: tags: | %% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | [© Copyright](../COPYRIGHT.md) | ||
... | ... |