Mentions légales du service

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

#101 Remove a remaining reference to Xeus cling.

parent d4596825
Branches
No related tags found
1 merge request!114Use CppyyKernel to run the notebooks instead of Xeus-cling.
%% 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:
## 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++
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++
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++
%%cpptoolbox cppyy/cppdef
// < without this line the perfectly valid - and recommended! - braces in data
// attribute initialization are not accepted by our kernel.
Vector::Vector()
: x_(0.), // braces may also be used since C++ 11... but not supported here by Xeus-cling.
y_(0.),
z_(0.)
: x_{0}, // braces may also be used since C++ 11, and are the recommended choice
y_{}, // the value inside may be skipped if it is 0
z_{}
{ }
```
%% Cell type:code id: tags:
``` c++
double Vector::Norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` c++
#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 [More Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) 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++
#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++
#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++
{
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:
## The importance of the `:` syntax
We saw just above that we may init data attributes either in the body of the constructor or before the body with the `:` syntax.
In the above example, both are fine and work as expected.
However, there are several cases for which you **must** use the `:` syntax; here are two of them.
%% Cell type:code id: tags:
``` c++
struct First
{ };
```
%% Cell type:code id: tags:
``` c++
struct Second
{
const First& first_;
Second(const First& first, int value);
const int value_;
};
```
%% Cell type:code id: tags:
``` c++
Second::Second(const First& first, int value)
{
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
value_ = value; // COMPILATION ERROR: can't change the value of a const variable!
}
```
%% Cell type:code id: tags:
``` c++
Second::Second(const First& first, int value)
: first_(first), // OK!
value_(value)
{ }
```
%% Cell type:markdown id: tags:
With the `:` syntax, the attributes are filled with their expected values _as soon as_ the object is created.
On the other hand, the body of the constructor is run _after_ the actual creation occurs.
So concerning data attributes if their value is set in the body of the constructor it is actually an _assignment_ that takes place and replace the default value built at construction.
The cases in which the data attributes **must** be defined by the `:` constructor are:
- When the data attribute can't be copied (this case covers both cases already seen).
- When the type of the data attribute doesn't foresee a default constructor (i.e. a constructor without arguments).
Anyway, when you can you should really strive to use the `:` syntax to define data attributes.
%% Cell type:markdown id: tags:
### Good practice: In the `:` syntax to initialize data attributes, define them in the same order as in the class declaration
%% Cell type:markdown id: tags:
For instance, don't do that:
%% Cell type:code id: tags:
``` c++
struct PoorlyDefinedVector
{
double x_;
double y_;
PoorlyDefinedVector(double x, double y);
};
```
%% Cell type:code id: tags:
``` c++
PoorlyDefinedVector::PoorlyDefinedVector(double x, double y)
: y_(y), // y_ is defined first, whereas x_ comes first in data attribute list!
x_(x)
{ }
```
%% Cell type:markdown id: tags:
You may have unwanted effects if you do not respect the same ordering.
Fortunately, compilers are able to emit warnings to advise you to remedy this if you have properly activated them with options such as `-Wall` (we shall discuss this in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb)). Here is an example when calling directly `clang`; you may also try with `gcc` which will also provides a warning:
%% Cell type:code id: tags:
``` c++
%%cpptoolbox clang
#include <cstdlib>
struct PoorlyDefinedVector
{
double x_;
double y_;
PoorlyDefinedVector(double x, double y);
};
PoorlyDefinedVector::PoorlyDefinedVector(double x, double y)
: y_(y), // y_ is defined first, whereas x_ comes first in data attribute list!
x_(x)
{ }
int main()
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## Delegating constructor
Since C++ 11, it is possible to use a base constructor when defining another constructor:
%% Cell type:code id: tags:
``` c++
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++
#include <iostream>
void Vector2::Print() const
{
std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl;
}
```
%% Cell type:code id: tags:
``` c++
#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++
Vector2::Vector2(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` c++
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++
{
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++
struct ClassWithoutConstructor
{ };
```
%% Cell type:code id: tags:
``` c++
{
ClassWithoutConstructor my_object;
}
```
%% Cell type:code id: tags:
``` c++
struct ClassWithConstructorWithArg
{
ClassWithConstructorWithArg(int a);
};
```
%% Cell type:code id: tags:
``` c++
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ }
```
%% Cell type:code id: tags:
``` c++
{
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 an 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++
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` c++
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` c++
{
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++
struct BadlyInitialized
{
int a_;
};
```
%% Cell type:code id: tags:
``` c++
#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++
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++
SafeClass::SafeClass(int new_value)
: a_(new_value)
{ }
```
%% Cell type:code id: tags:
``` c++
#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: use `explicit` constructors by default
Let's study the following case:
%% Cell type:code id: tags:
``` c++
struct ClassWithIntConstructor
{
ClassWithIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` c++
#include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` c++
{
ClassWithIntConstructor my_object(5);
my_object = 7; // 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 [Effective Modern C++](../bibliography.ipynb#Effective-Modern-C++).
%% Cell type:code id: tags:
``` c++
struct ClassWithExplicitIntConstructor
{
explicit ClassWithExplicitIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` c++
#include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` c++
{
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++
struct Array
{
Array(int unique_id, std::size_t array_size);
~Array(); // Destructor!
double* underlying_array_ = nullptr;
const int unique_id_;
};
```
%% Cell type:code id: tags:
``` c++
Array::Array(int unique_id, std::size_t array_size)
: unique_id_(unique_id)
{
underlying_array_ = new double[array_size];
}
```
%% Cell type:code id: tags:
``` c++
#include <iostream>
Array::~Array()
{
std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl;
delete[] underlying_array_;
}
```
%% Cell type:code id: tags:
``` c++
{
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++
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:
[© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment