Commit a22d9799 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Constructors: add the fact attributes ordering do matter in constructor.

parent 80d14bc4
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [(Base) constructors and destructor](/notebooks/2-ObjectProgramming/3-constructors-destructor.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-base-constructor" data-toc-modified-id="Introduction-to-base-constructor-1">Introduction to base constructor</a></span></li><li><span><a href="#Initializing-a-reference-data-attribute" data-toc-modified-id="Initializing-a-reference-data-attribute-2">Initializing a reference data attribute</a></span></li><li><span><a href="#Delegating-constructor" data-toc-modified-id="Delegating-constructor-3">Delegating constructor</a></span></li><li><span><a href="#Default-constructor" data-toc-modified-id="Default-constructor-4">Default constructor</a></span></li><li><span><a href="#Good-practice:-data-attribute-initialization" data-toc-modified-id="Good-practice:-data-attribute-initialization-5">Good practice: data attribute initialization</a></span></li><li><span><a href="#Good-practice:-use-explicit-constructors-by-default" data-toc-modified-id="Good-practice:-use-explicit-constructors-by-default-6">Good practice: use <code>explicit</code> constructors by default</a></span></li><li><span><a href="#Destructor" data-toc-modified-id="Destructor-7">Destructor</a></span><ul class="toc-item"><li><span><a href="#Default-destructor" data-toc-modified-id="Default-destructor-7.1">Default destructor</a></span></li></ul></li></ul></div>
<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">[WARNING] How to call a constructor without argument</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.),
: x_(0.),
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:** There is a technicality for constructor without arguments: they must be called without parenthesis (the reason is a possible confusion with a [functor](/notebooks/3-Operators/5-Functors.ipynb) - see Item 6 of \cite{Meyers2001} or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
### [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](/notebooks/3-Operators/5-Functors.ipynb) - see Item 6 of \cite{Meyers2001} or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v; // no parenthesis here!
std::cout << v.norm() << std::endl;
}
```
%% 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 arguments, you do not want your end-user to bypass this with a inconsiderate call to a constructor without arguments!
If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithAndWithoutArg my_object; // OK!
}
```
%% Cell type:markdown id: tags:
## Good practice: data attribute initialization
## 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 in 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`](/notebooks/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
%% Cell type:markdown id: tags:
In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](/notebooks/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
## Good practice: define the attribute in the same order they are defined
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:
%% 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; // 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 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
#include <limits> // for std::numeric_limits
struct Array
{
Array(int unique_id, std::size_t array_size);
~Array(); // Destructor!
double* array_ = nullptr;
const int unique_id_ = std::numeric_limits<int>::min(); // to provide absurd value if not initialized
// That can't happen here, but might if a constructor
// was added later on.
};
```
%% 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-2019_