Commit 9221c943 authored by STEFF Laurent's avatar STEFF Laurent
Browse files

fixed typos in op

parent 41a90447
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [(Base) constructors and destructor](./3-constructors-destructor.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-base-constructor" data-toc-modified-id="Introduction-to-base-constructor-1">Introduction to base constructor</a></span><ul class="toc-item"><li><span><a href="#[WARNING]-How-to-call-a-constructor-without-argument" data-toc-modified-id="[WARNING]-How-to-call-a-constructor-without-argument-1.1"><strong>[WARNING]</strong> How to call a constructor without argument</a></span></li><li><span><a href="#&quot;Auto-to-stick&quot;-syntax-for-constructor-calls" data-toc-modified-id="&quot;Auto-to-stick&quot;-syntax-for-constructor-calls-1.2">"Auto-to-stick" syntax for constructor calls</a></span></li></ul></li><li><span><a href="#Initializing-a-reference-data-attribute" data-toc-modified-id="Initializing-a-reference-data-attribute-2">Initializing a reference data attribute</a></span></li><li><span><a href="#Delegating-constructor" data-toc-modified-id="Delegating-constructor-3">Delegating constructor</a></span></li><li><span><a href="#Default-constructor" data-toc-modified-id="Default-constructor-4">Default constructor</a></span></li><li><span><a href="#Good-practice:-provide-in-the-data-attribute-declaration-a-default-value" data-toc-modified-id="Good-practice:-provide-in-the-data-attribute-declaration-a-default-value-5">Good practice: provide in the data attribute declaration a default value</a></span></li><li><span><a href="#Good-practice:-define-the-attribute-in-the-same-order-they-are-defined" data-toc-modified-id="Good-practice:-define-the-attribute-in-the-same-order-they-are-defined-6">Good practice: define the attribute in the same order they are defined</a></span></li><li><span><a href="#Good-practice:-use-explicit-constructors-by-default" data-toc-modified-id="Good-practice:-use-explicit-constructors-by-default-7">Good practice: use <code>explicit</code> constructors by default</a></span></li><li><span><a href="#Destructor" data-toc-modified-id="Destructor-8">Destructor</a></span><ul class="toc-item"><li><span><a href="#Default-destructor" data-toc-modified-id="Default-destructor-8.1">Default destructor</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction to base constructor
In fact, our previous `init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.
Several constructors may be defined for a given class, provided there is no signature overlap.
%% Cell type:code id: tags:
``` C++17
struct Vector
{
double x_;
double y_;
double z_;
Vector(); // Constructor
Vector(double x, double y, double z); // Another constructor
double norm() const;
};
```
%% Cell type:markdown id: tags:
Data attributes may be initialized more efficiently with a special syntax shown below:
%% Cell type:code id: tags:
``` C++17
Vector::Vector(double x, double y, double z)
: x_(x), // See the syntax here: `:` to introduce data attributes initialization,
y_(y), // and commas to separate the different data attributes.
z_(z)
{
}
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector()
: x_(0.), // braces may also be used since C++ 11... but not supported here by Xeus-cling.
y_(0.),
z_(0.)
{ }
```
%% Cell type:code id: tags:
``` C++17
double Vector::norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v(5., 6., -4.2); // note the creation of an object with a constructor call.
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
### **[WARNING]** How to call a constructor without argument
There is a technicality for constructor without arguments: they must be called **without** parenthesis (the reason is a possible confusion with a [functor](../3-Operators/5-Functors.ipynb) - see Item 6 of \cite{Meyers2001} or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v; // no parenthesis here!
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
### "Auto-to-stick" syntax for constructor calls
A way to avoid the mistake entirely is to call the so-called "auto-to-stick" alternate syntax:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
auto v = Vector(5, 10, 15); // auto-to-stick syntax: a new perfectly fine way to declare an object.
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
This syntax completely removes the ambiguity: you **have to** keep the `()`, so the constructor with no arguments doesn't become a special case that is handled differently.
%% Cell type:code id: tags:
``` C++17
{
auto v = Vector(); // auto-to-stick syntax
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
This auto-to-stick syntax is not widely used, but is advised by some developers as a natural evolution of the syntax of the language (it is very akin to the [alternate syntax for functions](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) we saw earlier).
I advise you to read this very interesting [FluentCpp post](https://www.fluentcpp.com/2018/09/28/auto-stick-changing-style/) about this syntax which ponders about the relunctance we might have to embrace evolution of our languages - so the reading is of interest even for developers using other languages.
%% Cell type:markdown id: tags:
## Initializing a reference data attribute
We saw above a new syntax to initialize data attributes, but there is no harm defining the same as we did in our former `init()` method. That is however not true when there is a reference data attribute, for which the reference must absolutely be defined with the new syntax:
%% Cell type:code id: tags:
``` C++17
struct First
{
};
```
%% Cell type:code id: tags:
``` C++17
struct Second
{
const First& first_;
Second(const First& first);
};
```
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
{
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
}
```
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
: first_(first) // OK!
{ }
```
%% Cell type:markdown id: tags:
The reason for this behaviour is that the reference must be defined at the very moment the object is created, and in fact the body of a constructor is run _after_ the actual creation occurs. With the proper syntax, the data is properly initialized in the same time the object is created.
## Delegating constructor
Since C++ 11, it is possible to use a base constructor when defining another constructor:
%% Cell type:code id: tags:
``` C++17
struct Vector2
{
double x_, y_, z_;
Vector2();
Vector2(double x);
Vector2(double x, double y, double z);
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector2::Print() const
{
std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Vector2::Vector2()
: x_(-1.),
y_(-1.),
z_(-1.)
{
std::cout << "Calling Vector2 constructor with no arguments." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
Vector2::Vector2(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector2::Vector2(double x)
: Vector2()
{
x_ = x; // As the first constructor is assumed to build fully the object, you can't assign data attributes
// before the body of the constructor.
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << "Constructor with no argument:" << std::endl;
Vector2 v1;
v1.Print();
std::cout << "Constructor with no delegation:" << std::endl;
Vector2 v2(3., 7., 5.);
v2.Print();
std::cout << "Constructor that calls a delegate constructor:" << std::endl;
Vector2 v3(3.);
v3.Print();
}
```
%% Cell type:markdown id: tags:
## Default constructor
If in a `class` or `struct` no constructor is defined, a default one is assumed: it takes no argument and sports an empty body.
As soon as another constructor is defined, this default constructor no longer exists:
%% Cell type:code id: tags:
``` C++17
struct ClassWithoutConstructor
{ };
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithoutConstructor my_object;
}
```
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithArg
{
ClassWithConstructorWithArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithArg my_object; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well thought-out arguments, you do not want your end-user to bypass this with a inconsiderate call to a constructor without arguments!
If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithAndWithoutArg my_object; // OK!
}
```
%% Cell type:markdown id: tags:
## Good practice: provide in the data attribute declaration a default value
In a constructor, you are expected to initialize properly all the data attributes. You can't expect a default behaviour if you fail to do so:
%% Cell type:code id: tags:
``` C++17
struct BadlyInitialized
{
int a_;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
BadlyInitialized my_object;
std::cout << "Undefined behaviour: no guarantee for the value of the data attribute!: " << my_object.a_ << std::endl;
}
```
%% Cell type:markdown id: tags:
You are therefore supposed to define explicitly all the data attributes in all of your constructors. It was easy to get trumped by this in C++98/03: if you added a new data attribute and forgot to initialize it in one of your constructor, you would have undefined behaviour that is one of the worst bug to track down! (as on your machine/architecture you may have a "good" behaviour haphazardly).
Fortunately, C++ 11 introduced a mechanism I strongly recommend to provide a default value:
%% Cell type:code id: tags:
``` C++17
struct SafeClass
{
int a_ { 5 }; // The default value is provided here in the class declaration.
SafeClass() = default;
SafeClass(int new_value);
};
```
%% Cell type:code id: tags:
``` C++17
SafeClass::SafeClass(int new_value)
: a_(new_value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
SafeClass no_Arg;
std::cout << "If constructor doesn't change the value, default is used: " << no_Arg.a_ << std::endl;
SafeClass modified(10);
std::cout << "If constructor changes the value, choice is properly used: " << modified.a_ << std::endl;
}
```
%% Cell type:markdown id: tags:
Please notice doing so doesn't prevent you to use the efficient initialization of `a_` in the constructor with arguments: the values thus provided in the data attributes definitions are used only if the constructor doesn't supersede them.
In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
%% Cell type:markdown id: tags:
## Good practice: define the attribute in the same order they are defined
## Good practice: define the attribute in the same order they are declared
Data attributes should be defined in the exact same order they are declared within the class.
%% Cell type:code id: tags:
``` C++17
struct BadOrder
{
BadOrder();
int a_;
bool b_;
};
```
%% Cell type:code id: tags:
``` C++17
BadOrder::BadOrder()
: b_(false), // WRONG: a_ should be defined before b_, as it is the order in which they are declared in the class.
a_(5)
{ }
```
%% Cell type:markdown id: tags:
Unfortunately Xeus-cling doesn't issue the warning here, but I gather all normal compilers do (even the oldest versions of clang and gcc available on [Wandbox](https://wandbox.org/) do in the very least...).
Most of the time it doesn't matter much, but if there are dependencies between attributes it might create havoc so take the habit to respect the ordering.
Of course, you may not declare explicitly a data attribute for which you provide a default value at declaration:
Of course, you may not define explicitly a data attribute for which you provide a default value at declaration:
%% Cell type:code id: tags:
``` C++17
#include <string>
struct OkOrder
{
OkOrder(int a, float c);
int a_ {0};
std::string b_ {"default_text"};
float c_ {3.5f};
};
```
%% Cell type:code id: tags:
``` C++17
OkOrder::OkOrder(int a, float c)
: a_(a),
c_(c) // Ok!
{ }
```
%% Cell type:markdown id: tags:
## Good practice: use `explicit` constructors by default
Let's study the following case:
%% Cell type:code id: tags:
``` C++17
struct ClassWithIntConstructor
{
ClassWithIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithIntConstructor my_object(5);
my_object = 7.9; // Dubious but correct: assigning an integer!
}
```
%% Cell type:markdown id: tags:
So what happens here? In fact, the compiler implicitly convert the integer read into a `ClassWithIntConstructor` constructor with an integer argument...
There are situations in which this might be deemed the right thing to do (none to my mind but I guess it depends on your programming style) but more often than not it's not what is intended and a good old compiler yell would be much preferable.
To do so, in C++ 11 you must stick the keyword **explicit** in the declaration in front of the constructor. Personally I tend to always provide it to my constructors, following the likewise advice by \cite{Meyers2015}.
%% Cell type:code id: tags:
``` C++17
struct ClassWithExplicitIntConstructor
{
explicit ClassWithExplicitIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithExplicitIntConstructor my_object(5);
my_object = 7; // COMPILATION ERROR! YAY!
}
```
%% Cell type:markdown id: tags:
## Destructor
%% Cell type:markdown id: tags:
The pendant of the constructor is the **destructor**, which is called when the object is terminated. Contrary to constructors, there is only one destructor for a given class, and by design it takes no parameter.
The syntax is like a constructor with no parameter with an additional `~` in front of the name.
%% Cell type:code id: tags:
``` C++17
struct Array
{
Array(int unique_id, std::size_t array_size);
~Array(); // Destructor!
double* array_ = nullptr;
const int unique_id_;
};
```
%% Cell type:code id: tags:
``` C++17
Array::Array(int unique_id, std::size_t array_size)
: unique_id_(unique_id)
{
array_ = new double[array_size];
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Array::~Array()
{
std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl;
delete[] array_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Array array1(1, 5ul);
{
Array array2(2, 3ul);
{
Array array3(3, 5ul);
}
Array array4(4, 2ul);
}
}
```
%% Cell type:markdown id: tags:
It's important to notice the ordering here: as soon as an object becomes out of scope, it is immediately destroyed; the creation order doesn't matter at all!
We will see a bit [later](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) how to take advantage of this behaviour to write programs that do not leak memory.
### Default destructor
If not specified, C++ implicitly defines a destructor with an empty body. Personally I like even in this case to make it explicit, which is done the same way as for a constructor from C++11 onward:
%% Cell type:code id: tags:
``` C++17
struct MyClass
{
MyClass() = default; // explicit default constructor
~MyClass() = default; // explicit default destructor
}
```
%% Cell type:markdown id: tags:
This is however a matter of personal taste; see for instance [this post from FluentCpp](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie) for the opposite advice of not defining explicitly default constructor / destructor if you don't have to.
%% Cell type:markdown id: tags:
# References
[<a id="cit-Meyers2001" href="#call-Meyers2001">Meyers2001</a>] Scott Meyers, ``_Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library_'', 2001.
[<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Public-and-private" data-toc-modified-id="Public-and-private-1">Public and private</a></span></li><li><span><a href="#Struct-and-class" data-toc-modified-id="Struct-and-class-2">Struct and class</a></span></li><li><span><a href="#Why-encapsulation?" data-toc-modified-id="Why-encapsulation?-3">Why encapsulation?</a></span></li><li><span><a href="#Good-practice:-data-attributes-should-be-private" data-toc-modified-id="Good-practice:-data-attributes-should-be-private-4">Good practice: data attributes should be private</a></span></li><li><span><a href="#Good-practices:-the-default-status-of-a-method-should-be-private-and-you-should-use-mutators-for-quantities-that-might-vary" data-toc-modified-id="Good-practices:-the-default-status-of-a-method-should-be-private-and-you-should-use-mutators-for-quantities-that-might-vary-5">Good practices: the default status of a method should be private and you should use mutators for quantities that might vary</a></span></li><li><span><a href="#Friendship" data-toc-modified-id="Friendship-6">Friendship</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## 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:
%% Cell type:code id: tags:
``` C++17
class FirstClass
{
FirstClass();
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
FirstClass::FirstClass()
{
std::cout << "Hello world!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
FirstClass object; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
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)):
* **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.
To determine which one is used, `public:` and `private:` may be added in a class declaration:
%% Cell type:code id: tags:
``` C++17
class SecondClass
{
public:
SecondClass(int a);
private:
int a_ { -9999999 }; // stupid default value.
void SetValue(int a);
public:
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
SecondClass::SecondClass(int a)
{
SetValue(a); // Ok: a member function (here the constructor) may call private method
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void SecondClass::Print() const
{
std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute.
}
```
%% Cell type:code id: tags:
``` C++17
void SecondClass::SetValue(int a)
{
a_ = a; // Ok: a member function may use private data attribute.
}
```
%% Cell type:code id: tags:
``` C++17
{
SecondClass object(5);
object.Print(); // Ok: public method
}
```
%% Cell type:code id: tags:
``` C++17
{
SecondClass object(5);
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method
}
```
%% 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 langage 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
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
The difference in a (C++) `struct` and a `class` is in fact thin-veiled:
* A `struct` assumes implicitly attributes are public if nothing is specified.
* A `class` assumes implicitly 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.
## 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 encapsulation benefits appear clearly:
%% Cell type:code id: tags:
``` C++17
struct Rectangle
{
Rectangle(double length, double width);
double length_;
double width_;
double area_;
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle::Rectangle(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Rectangle rect(5., 4.);
rect.Print(); // OK
rect.length_ = 23.;
rect.Print(); // Not so much...
}
```
%% Cell type:markdown id: tags:
Encapsulation may protect from that:
%% Cell type:code id: tags:
``` C++17
class MoreSecureRectangle
{
public:
MoreSecureRectangle(double length, double width);
void Print() const;
private:
double length_;
double width_;
double area_;
};
```
%% Cell type:code id: tags:
``` C++17
MoreSecureRectangle::MoreSecureRectangle(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void MoreSecureRectangle::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
MoreSecureRectangle rect(5., 4.);
rect.Print(); // OK
rect.length_ = 0.; // can't do that!
}
```
%% 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:
%% Cell type:code id: tags:
``` C++17
class Rectangle3
{
public:
Rectangle3(double length, double widgth);
void Print() const;
void SetLength(double x);
void SetWidth(double x);
private:
double length_;
double width_;
double area_;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle3::Rectangle3(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle3::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle3::SetLength(double x)
{
length_ = x;
area_ = length_ * width_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle3::SetWidth(double x)
{
width_ = x;
area_ = length_ * width_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Rectangle3 rect(5., 4.);
rect.Print(); // OK
rect.SetLength(23.);
rect.Print(); // OK
}
```
%% Cell type:markdown id: tags:
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 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 here. 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:
## 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.
It is often advised (see for instance item 22 of \cite{Meyers2005}) 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:
``` C++17
class Rectangle4
{
public:
Rectangle4(double length, double width);
// Mutators
void SetLength(double x);
void SetWidth(double x);
// Accessors
double GetLength() const;
double GetWidth() const;
private:
double length_ { -1.e20 }; // a stupid value which at least is deterministically known...
double width_ { -1.e20 };
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle4:: Rectangle4(double length, double width)
: length_(length),
width_(width)
{ }
```
%% Cell type:code id: tags:
``` C++17
void Rectangle4::SetLength(double x)
{
length_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle4::SetWidth(double x)
{
width_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle4::GetLength() const
{
return length_;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle4::GetWidth() const
{
return width_;
}
```
%% Cell type:code id: tags:
``` C++17
double ComputeArea(const Rectangle4& r) // free function
{
return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Rectangle4 rect(4., 6.5);
std::cout << "Area = " << ComputeArea(rect) << std::endl;
rect.SetWidth(5.);
std::cout << "Area = " << ComputeArea(rect) << std::endl;
}
```
%% Cell type:markdown id: tags:
## Good practices: 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:
%% Cell type:code id: tags:
``` C++17
class Rectangle5
{
public:
Rectangle5(double length, double width);
// Mutators
void SetLength(double x);
void SetWidth(double x);
// Accessor
double GetArea() const; // others accessors are not defined to limit steps to define the class
private:
// Mutator for the area
// Prototype is not bright (parameters are clearly intended to be the data attributes
// and a prototype with no parameters would be much wiser!)
// but is useful to make my point below.
void SetArea(double length, double width);
private:
double area_ { -1.e20 }; // a stupid value which at least is deterministic...
double length_ { -1.20 };
double width_ { -1.20 };
};
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetArea(double length, double width)
{
area_ = width * length;
}
```
%% Cell type:code id: tags:
``` C++17
Rectangle5:: Rectangle5(double length, double width)
: length_(length),
width_(width)
{
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle5::GetArea() const
{
return area_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetLength(double x)
{
length_ = x;
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetWidth(double x)
{
width_ = x;
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Print5(const Rectangle5& r)
{
std::cout << "Area is " << r.GetArea() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Rectangle5 rect(3., 4.);
Print5(rect);
rect.SetLength(8.);
Print5(rect);
rect.SetWidth(2.);
Print5(rect);
}
```
%% 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).
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 him.
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:
## 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:
%% Cell type:code id: tags:
``` C++17
#include <limits>
class Vector
{
public:
Vector(double x, double y, double z);
friend double norm(const Vector&);
friend class PrintVector; // In C++11 `class` became optional here.
private:
double x_ = std::numeric_limits<double>::min();
double y_ = std::numeric_limits<double>::min();
double z_ = std::numeric_limits<double>::min();
};
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <cmath>
double norm(const Vector& v)
{
return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK!
}
```
%% Cell type:code id: tags:
``` C++17
class PrintVector // not the cleverest class we could write...
{
public:
PrintVector(const Vector& v);
void Print() const;
private:
const Vector& vector_;
};
```
%% Cell type:code id: tags:
``` C++17
PrintVector::PrintVector(const Vector& v)
: vector_(v)
{}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintVector::Print() const
{
std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_
<< ", " << vector_.z_ << ")." << std::endl; // OK because of friendship!
}
```
%% Cell type:code id: tags:
``` C++17
Vector v(2., 3., 5.);
PrintVector printer(v);
print_object.Print();
printer.Print();
```
%% Cell type:markdown id: tags:
Obviously, friendship should be used with parcimony... 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 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 [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration) is an exception to current statement).
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers2005" href="#call-Meyers2005">Meyers, 2005</a>) Scott Meyers, ``_Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)_'', 2005.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Static attributes](./5-static.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Static-in-C" data-toc-modified-id="Static-in-C-1">Static in C</a></span></li><li><span><a href="#Static-methods" data-toc-modified-id="Static-methods-2">Static methods</a></span><ul class="toc-item"><li><span><a href="#Nitpick:-ClassName()-is-very-inefficient" data-toc-modified-id="Nitpick:-ClassName()-is-very-inefficient-2.1">Nitpick: <code>ClassName()</code> is very inefficient</a></span></li></ul></li><li><span><a href="#Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)" data-toc-modified-id="Static-data-attributes---to-avoid...-(see-next-section-to-understand-why!)-3">Static data attributes - to avoid... (see next section to understand why!)</a></span></li><li><span><a href="#Static-order-initialization-fiasco---and-its-fix" data-toc-modified-id="Static-order-initialization-fiasco---and-its-fix-4">Static order initialization fiasco - and its fix</a></span><ul class="toc-item"><li><span><a href="#New-C++-17-fix" data-toc-modified-id="New-C++-17-fix-4.1">New C++ 17 fix</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Static in C
As a reminder, we have seen in a [previous notebook](./1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C.
As a reminder, we have seen in a [previous notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C.
What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idiom presented here).
%% Cell type:markdown id: tags:
## Static methods
%% Cell type:markdown id: tags:
Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a **static** keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.
Static attributes are following the exact same rules as the standard ones regarding the access status (public or private).
%% Cell type:code id: tags:
``` C++17
#include <string>
struct Class
{
static std::string ClassName();
Class() = default;
};
```
%% Cell type:code id: tags:
``` C++17
std::string Class::ClassName()
{
return "Class";
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << "A static method may be called without any object instantiated: "
<< Class::ClassName() << std::endl;
Class obj;
std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl;
}
```
%% Cell type:markdown id: tags:
### Nitpick: `ClassName()` is very inefficient
The method above is highly inefficient: the method reinstantiates a new string each time the method is called!
A better implementation would be to use the `static` we already knew from C:
%% Cell type:code id: tags:
``` C++17
#include <string>
struct Class2
{
static const std::string& ClassName();
Class2() = default;
};
```
%% Cell type:code id: tags:
``` C++17
const std::string& Class2::ClassName()
{
static std::string ret = "Class2"; // on the very first call, the string is allocated
return ret; // on any calls, a reference to this instantiation is returned.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << "A static method may be called without any object instantiated: "
<< Class2::ClassName() << std::endl;
Class2 obj;
std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl;
}
```
%% Cell type:markdown id: tags:
We'll see this way of doing thing will solve an issue with data attributes we'll see immediately.
%% Cell type:markdown id: tags:
## Static data attributes - to avoid... (see next section to understand why!)
**Xeus-Cling Warning:** cling doesn't enable proper initialization of a static data atribute... Please considered the following code, available [@Coliru](https://coliru.stacked-crooked.com/a/f43bcc4548a4f160):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
struct Class
{
Class();
~Class();
static int Ninstance_;
};
Class::Class()
{
++Ninstance_;
}
Class::~Class()
{
--Ninstance_;
}
// IMPORTANT: this line must be put in a compiled file!
int Class::Ninstance_ = 0;
void Print()
{
std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl;
}
int main(int argc, char** argv)
{
Print();
Class obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl;
Print();
{
std::vector<Class> vec(5);
Print();
}
Print();
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## Static order initialization fiasco - and its fix
However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
#include <vector>
struct Class3
{
Class3();
~Class3();
static int& Ninstance(); // notice the reference and the fact it's now a method
};
```
%% Cell type:code id: tags:
``` C++17
int& Class3::Ninstance()
{
static int ret = 0; // the initial value, please notice the use of C static here!
return ret;
}
```
%% Cell type:code id: tags:
``` C++17
Class3::Class3()
{
Ninstance()++;
}
```
%% Cell type:code id: tags:
``` C++17
Class3::~Class3()
{
Ninstance()--;
}
```
%% Cell type:code id: tags:
``` C++17
void Print3()
{
std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Print3();
Class3 obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl;
Print3();
{
Class3* vec = new Class3[5];
Print3();
delete[] vec;
}
Print3();
}
```
%% Cell type:markdown id: tags:
To understand better the possible issue and the fix proposed, you may have a look at:
* Item 26 of \cite{Meyers1995}
* The dedicated item on [isocpp FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order)
%% Cell type:markdown id: tags:
### New C++ 17 fix
C++ 17 actually provides a way to define the value in the header file with the `inline` keyword:
%% Cell type:code id: tags:
``` C++17
struct Class4
{
Class4();
~Class4();
static inline int Ninstance_ = 0;
};
```
%% Cell type:markdown id: tags:
Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave me the hint!
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers1995" href="#call-Meyers1995">Meyers, 1995</a>) Scott Meyers, ``_More Effective C++: 35 New Ways to Improve Your Programs and Designs_'', 1995.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-inheritance" data-toc-modified-id="Introduction-to-inheritance-1">Introduction to inheritance</a></span><ul class="toc-item"><li><span><a href="#Multiple-layer-of-inheritance" data-toc-modified-id="Multiple-layer-of-inheritance-1.1">Multiple layer of inheritance</a></span></li><li><span><a href="#Multiple-inheritance" data-toc-modified-id="Multiple-inheritance-1.2">Multiple inheritance</a></span></li><li><span><a href="#Diamond-inheritance" data-toc-modified-id="Diamond-inheritance-1.3">Diamond inheritance</a></span></li></ul></li><li><span><a href="#Public-inheritance,-private-inheritance-and-composition" data-toc-modified-id="Public-inheritance,-private-inheritance-and-composition-2">Public inheritance, private inheritance and composition</a></span><ul class="toc-item"><li><span><a href="#IS-A-relationship-of-public-inheritance" data-toc-modified-id="IS-A-relationship-of-public-inheritance-2.1">IS-A relationship of public inheritance</a></span></li><li><span><a href="#IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-private-inheritance" data-toc-modified-id="IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-private-inheritance-2.2">IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance</a></span></li><li><span><a href="#CONTAINS-A-relationship-of-composition" data-toc-modified-id="CONTAINS-A-relationship-of-composition-2.3">CONTAINS-A relationship of composition</a></span></li><li><span><a href="#Private-inheritance-vs-composition" data-toc-modified-id="Private-inheritance-vs-composition-2.4">Private inheritance vs composition</a></span></li></ul></li><li><span><a href="#Protected-status" data-toc-modified-id="Protected-status-3">Protected status</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction to inheritance
Sometimes, you might want to define two types that are related: one might be an extension of the other, or they may share some similarities we would like to put in common.
Let's suppose for instance you are asked to register all vehicles owned by your company. You could define independent classes `Bicycle`, `Scooter` and `Car`, but as a result storing them in a same array (or even better `std::vector`) would be impossible.
The idea of inheritance is to provide a **base class** from which our classes are derived:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
enum class motor_type { none, thermic, electric };
class Vehicle
{
public:
Vehicle(motor_type type);
void Print() const { std::cout << "I'm a Vehicle!" << std::endl; }
private:
motor_type type_;
};
```
%% Cell type:code id: tags:
``` C++17
Vehicle::Vehicle(motor_type type)
: type_(type)
{ }
```
%% Cell type:code id: tags:
``` C++17
class ElectricBicycle : public Vehicle
{
public:
ElectricBicycle();
};
```
%% Cell type:code id: tags:
``` C++17
class ThermicCar : public Vehicle
{
public:
ThermicCar();
};
```
%% Cell type:code id: tags:
``` C++17
ElectricBicycle::ElectricBicycle()
: Vehicle(motor_type::electric)
{ }
```
%% Cell type:code id: tags:
``` C++17
ThermicCar::ThermicCar()
: Vehicle(motor_type::thermic)
{ }
```
%% Cell type:markdown id: tags:
A bit of syntax first:
* See the structure in declaring the derived classes: there is a `:` followed by the keyword `public` and the name of the class.
* The derived constructors must first call one of the base class constructor. If none specified, the default one without arguments is called *if existing*...
The base class part is constructed first, and then the elements specific to the derived class are added (that will be important - we will come back to that).
Likewise, destruction is performed in reverse order: first the specific part of the derived class, then the base class (in most cases you don't have to care about that).
### Multiple layer of inheritance
A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)).
%% Cell type:code id: tags:
``` C++17
class ElectricVehicle : public Vehicle
{
public:
ElectricVehicle();
};
```
%% Cell type:code id: tags:
``` C++17
ElectricVehicle::ElectricVehicle()
: Vehicle(motor_type::electric)
{ }
```
%% Cell type:code id: tags:
``` C++17
class ElectricCar : public ElectricVehicle
{
public:
ElectricCar() = default;
};
```
%% Cell type:markdown id: tags:
### Multiple inheritance
It is also possible for a class to inherit from several parents:
%% Cell type:code id: tags:
``` C++17
class BlueObjects
{
public:
BlueObjects() = default;
int Print()
{
std::cout << "I'm blue!" << std::endl;
return 42;
}
}
```
%% Cell type:code id: tags:
``` C++17
class BlueVehicle : public Vehicle, public BlueObjects
{
public:
BlueVehicle(motor_type type);
};
```
%% Cell type:code id: tags:
``` C++17
BlueVehicle::BlueVehicle(motor_type type)
: Vehicle(type), // mandatory call of the non-default constructor
BlueObjects() // not mandatory: default constructor called
{ }
```
%% Cell type:code id: tags:
``` C++17
BlueVehicle blue_bike(motor_type::none);
```
%% Cell type:markdown id: tags:
**Beware:** there might be ambiguity between some methods names, regardless of prototype:
%% Cell type:code id: tags:
``` C++17
blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ?
```
%% Cell type:markdown id: tags:
It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place:
%% Cell type:code id: tags:
``` C++17
blue_bike.BlueObjects::Print();
```
%% Cell type:code id: tags:
``` C++17
blue_bike.Vehicle::Print();
```
%% Cell type:markdown id: tags:
### Diamond inheritance
In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`:
%% Cell type:markdown id: tags:
<img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/>
%% Cell type:markdown id: tags:
I really advise you to avoid this kind of things whenever possible, but if you _really_ need this:
- First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation?
- Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line).
%% Cell type:markdown id: tags:
## Public inheritance, private inheritance and composition
### IS-A relationship of public inheritance
So far, we have derived **publicly** the base class (hence the **public** keyword in the inheritance declaration), and this defines a **IS-A** relationship: the derived object is expected to be acceptable *everywhere* a base object is deemed acceptable.
This might seem trivial, but is not as obvious at it seems. Let's consider a counter-intuitive example:
%% Cell type:code id: tags:
``` C++17
#include <string>
class Rectangle
{
public:
Rectangle(double width, double length);
void SetWidth(double x);
void SetLength(double x);
double GetWidth() const;
double GetLength() const;
void Print() const;
private:
double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized
// - std::optional (C++17) would probably a better choice.
double length_ { -1.e20 }
double length_ { -1.e20 } ;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle::Rectangle(double width, double length)
: width_(width),
length_(length)
{ }
```
%% Cell type:code id: tags:
``` C++17
double Rectangle::GetWidth() const
{
return width_;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle::GetLength() const
{
return length_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle::SetWidth(double x)
{
width_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle::SetLength(double x)
{
length_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle::Print() const
{
std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
class Square : public Rectangle // BAD IDEA!
{
public:
Square(double side_dimension);
};
```
%% Cell type:code id: tags:
``` C++17
Square::Square(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:markdown id: tags:
This is perfectly valid C++ code, and you might be happy with that... Now let's add a free-function that changes the shape of a rectangle:
%% Cell type:code id: tags:
``` C++17
void ModifyRectangle(Rectangle& r)
{
r.SetWidth(r.GetWidth() * 2.);
r.SetLength(r.GetLength() * .5);
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << " ==== RECTANGLE ==== " << std::endl;
Rectangle r(3., 5.);
r.Print();
ModifyRectangle(r); // ok
r.Print();
std::cout << " ==== SQUARE ==== " << std::endl;
Square c(4.);
c.Print(); // ok
ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency!
c.Print(); // urgh...
}
```
%% Cell type:markdown id: tags:
So the language allows you to define this public relationship between `Rectangle` and `Square`, but you can see it is not a very bright idea... (this example is more detailed in item 32 of \cite{Meyers2005}).
Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best.
The public inheritance is an application of the [Liskov substitution principe](https://en.wikipedia.org/wiki/Liskov_substitution_principle).
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.
### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance
What you might look at in fact is **private** inheritance, in which all the inherited attributes are considered private:
What you might look for in fact is **private** inheritance, in which all the inherited attributes are considered private:
%% Cell type:code id: tags:
``` C++17
class Square2 : private Rectangle
{
public:
Square2(double side_dimension);
};
```
%% Cell type:code id: tags:
``` C++17
Square2::Square2(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
Square2 square(4.);
square.SetWidth(5.); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
And from there:
%% Cell type:code id: tags:
``` C++17
{
Square2 square(4.);
ModifyRectangle(square); // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept.
And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy):
%% Cell type:code id: tags:
``` C++17
class Square3 : private Rectangle
{
public:
Square3(double side_dimension);
void SetSideDimension(double x);
using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle
};
```
%% Cell type:code id: tags:
``` C++17
Square3::Square3(double side_dimension)
: Rectangle(side_dimension, side_dimension)
{ }
```
%% Cell type:code id: tags:
``` C++17
void Square3::SetSideDimension(double x)
{
SetWidth(x); // the methods from Rectangle, accessible privately
SetLength(x);
}
```
%% Cell type:code id: tags:
``` C++17
{
Square3 square(4.);
square.Print();
square.SetSideDimension(3.);
square.Print();
}
```
%% Cell type:markdown id: tags:
### CONTAINS-A relationship of composition
Private inheritance is not the only way to provide this kind of behaviour; it is possible as well to use **composition**, which is defining a data attribute object that handles part of the computation.
I won't dwelve into too much details here, but the general idea is that composition is often preferable as the binding is less strong and you should strive for the looser relationship possible. Here is the same example as above implemented with composition:
%% Cell type:code id: tags:
``` C++17
class Square4
{
public:
Square4(double side_dimension);
~Square4();
void Print() const;
void SetSideDimension(double x);
private:
Rectangle& GetRectangle() const;
private:
Rectangle* rectangle_ = nullptr;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle& Square4::GetRectangle() const
{
return *rectangle_;
}
```
%% Cell type:code id: tags:
``` C++17
Square4::Square4(double side_dimension)
{
rectangle_ = new Rectangle(side_dimension, side_dimension);
}
```
%% Cell type:code id: tags:
``` C++17
Square4::~Square4()
{
delete rectangle_;
}
```
%% Cell type:code id: tags:
``` C++17
void Square4::SetSideDimension(double x)
{
auto& rectangle = GetRectangle();
rectangle.SetWidth(x);
rectangle.SetLength(x);
}
```
%% Cell type:code id: tags:
``` C++17
void Square4::Print() const
{
GetRectangle().Print();
}
```
%% Cell type:code id: tags:
``` C++17
{
Square4 square(4.);
square.Print();
square.SetSideDimension(3.);
square.Print();
}
```
%% Cell type:markdown id: tags:
You may have noticed the data attribute is stored as a pointer: this avoids issues related to the initialization of the object when it uses a non-default constructor. This doesn't mean using directly an object is impossible, just that extra conditions must be fulfilled in this case.
%% Cell type:markdown id: tags:
### Private inheritance vs composition
So what are the pros and cons of private inheritance and composition?
- No granularity with private inheritance: you get all the interface available (privately) in the derived class. On the other hand, with composition you can choose which method you want to expose publicly in your new class (only `Print()` here).
- But composition is more verbosy: if you want most of the interface, you need for each method to define a method which under the hood calls your data attribute (as did `Square4::Print()` above). So in you need most of the API of the base class private inheritance is less work.
- But composition is more verbosy: if you want most of the interface, you need for each method to define a method which under the hood calls your data attribute (as did `Square4::Print()` above). So if you need most of the API of the base class private inheritance is less work.
As indicated before, I tend to use more composition, but it's nice anyway to know both are available.
%% Cell type:markdown id: tags:
## Protected status
Our discussion about public and private inheritance has highlighted the importance of the propagation of status of public members:
* With public inheritance, public members of the base class remains public.
* With private inheritance, public members of the base class become private.
So far, we have not talked about what happens to private members... Now is the time; let's find out!
%% Cell type:code id: tags:
``` C++17
class BaseClass
{
public:
BaseClass() = default;
private:
void DoNothing() { }
};
```
%% Cell type:code id: tags:
``` C++17
class DerivedClass : public BaseClass
{
public:
DerivedClass();
};
```
%% Cell type:code id: tags:
``` C++17
DerivedClass::DerivedClass()
{
DoNothing(); // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
So a private method is not accessible, even to the derived class. If you need such a functionality (and you should!), there is actually a third keyword: **protected**. This status means basically private except for the derived classes:
%% Cell type:code id: tags:
``` C++17
class BaseClass2
{
public:
BaseClass2() = default;
protected:
void DoNothing() { }
};
```
%% Cell type:code id: tags:
``` C++17
class DerivedClass2 : public BaseClass2
{
public:
DerivedClass2();
};
```
%% Cell type:code id: tags:
``` C++17
DerivedClass2::DerivedClass2()
{
DoNothing(); // Ok
}
```
%% Cell type:code id: tags:
``` C++17
auto object = DerivedClass2();
```
%% Cell type:code id: tags:
``` C++17
object.DoNothing(); // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`.
%% Cell type:markdown id: tags:
There is as well a **protected** status for inheritance even if I must admit I have never needed it. A contributor to [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance) sumed this up with this nice cheatsheet:
%% Cell type:markdown id: tags:
![Diagram](Images/PublicProtectedPrivateDiagram.jpg "Inheritance diagram, courtesy of [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance)")
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers2005" href="#call-Meyers2005">Meyers, 2005</a>) Scott Meyers, ``_Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)_'', 2005.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......