"// < without this line the perfectly valid - and recommended! - braces in data \n",
"// attribute initialization are not accepted by our kernel.\n",
" \n",
"Vector::Vector()\n",
": x_(0.), // braces may also be used since C++ 11... but not supported here by Xeus-cling.\n",
"y_(0.),\n",
"z_(0.)\n",
": x_{0}, // braces may also be used since C++ 11, and are therecommended choice \n",
"y_{}, // the value inside may be skipped if it is 0\n",
"z_{}\n",
"{ }"
]
},
...
...
%% 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++
structVector
{
doublex_;
doubley_;
doublez_;
Vector();// Constructor
Vector(doublex,doubley,doublez);// Another constructor
doubleNorm()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(doublex,doubley,doublez)
: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++
%%cpptoolboxcppyy/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 therecommended choice
y_{},// the value inside may be skipped if it is 0
z_{}
{}
```
%% Cell type:code id: tags:
``` c++
doubleVector::Norm()const
{
returnstd::sqrt(x_*x_+y_*y_+z_*z_);
}
```
%% Cell type:code id: tags:
``` c++
#include<iostream>
{
Vectorv(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>
{
Vectorv;// 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>
{
autov=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++
{
autov=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++
structFirst
{};
```
%% Cell type:code id: tags:
``` c++
structSecond
{
constFirst&first_;
Second(constFirst&first,intvalue);
constintvalue_;
};
```
%% Cell type:code id: tags:
``` c++
Second::Second(constFirst&first,intvalue)
{
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(constFirst&first,intvalue)
: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
: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:
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):
## 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++
structBadlyInitialized
{
inta_;
};
```
%% Cell type:code id: tags:
``` c++
#include<iostream>
{
BadlyInitializedmy_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++
structSafeClass
{
inta_{5};// The default value is provided here in the class declaration.
SafeClass()=default;
SafeClass(intnew_value);
};
```
%% Cell type:code id: tags:
``` c++
SafeClass::SafeClass(intnew_value)
:a_(new_value)
{}
```
%% Cell type:code id: tags:
``` c++
#include<iostream>
{
SafeClassno_Arg;
std::cout<<"If constructor doesn't change the value, default is used: "<<no_Arg.a_<<std::endl;
SafeClassmodified(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
std::cout<<"Constructor called with argument "<<a<<std::endl;
}
```
%% Cell type:code id: tags:
``` c++
{
ClassWithIntConstructormy_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++).
std::cout<<"Constructor called with argument "<<a<<std::endl;
}
```
%% Cell type:code id: tags:
``` c++
{
ClassWithExplicitIntConstructormy_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++
structArray
{
Array(intunique_id,std::size_tarray_size);
~Array();// Destructor!
double*underlying_array_=nullptr;
constintunique_id_;
};
```
%% Cell type:code id: tags:
``` c++
Array::Array(intunique_id,std::size_tarray_size)
:unique_id_(unique_id)
{
underlying_array_=newdouble[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++
{
Arrayarray1(1,5ul);
{
Arrayarray2(2,3ul);
{
Arrayarray3(3,5ul);
}
Arrayarray4(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++
structMyClass
{
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.