"At the time of this writing, this makes the kernel crash... In a more advanced environment ([Wandbox](https://wandbox.org) for instance) the reason appears more clearly:"
"At the time of this writing, this makes the kernel crash... In a more realistic environment (below when the code is compiled with clang and executed, but you can play with [Wandbox](https://wandbox.org) if you want to see how the different compilers handle it) the reason appears more clearly:"
]
},
{
"cell_type": "markdown",
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"```txt\n",
"** Error in `./prog.exe': double free or corruption (fasttop): 0x0000000001532da0 **\n",
"```\n",
"%%cpptoolbox clang\n",
"// Exact same code as abovce, but given to your clang++ compiler to be compiled and executed\n",
"\n",
"#include <iostream>\n",
"\n",
"class Vector2\n",
"{\n",
" public:\n",
" Vector2(double x, double y, double z);\n",
" \n",
" ~Vector2();\n",
" \n",
" void Print(std::ostream& out) const;\n",
" \n",
" private:\n",
" \n",
" double* array_ { nullptr };\n",
"};\n",
"\n",
"Vector2::Vector2(double x, double y, double z)\n",
"So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice.\n",
"\n",
"One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!):\n"
...
...
@@ -203,6 +265,8 @@
"metadata": {},
"outputs": [],
"source": [
"%%cpptoolbox cppyy/cppdef\n",
"\n",
"#include <iostream>\n",
"\n",
"class Vector3\n",
...
...
@@ -211,22 +275,14 @@
" Vector3(double x, double y, double z);\n",
" \n",
" ~Vector3();\n",
" \n",
" // Then again the Xeus-cling issue with out of class operator definition.\n",
" Vector3& operator=(const Vector3& rhs)\n",
" {\n",
" // Array already initialized in constructor; just change its content.\n",
" for (auto i = 0ul; i < 3ul; ++i)\n",
" array_[i] = rhs.array_[i];\n",
" \n",
" return *this; // The (logical) return value for such a method.\n",
" // Array already initialized in constructor; just change its content.\n",
" for (auto i = 0ul; i < 3ul; ++i)\n",
" array_[i] = rhs.array_[i];\n",
" \n",
" return *this; // The (logical) return value for such a method.\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
...
...
@@ -292,7 +364,7 @@
"source": [
"### Uncopyable class\n",
"\n",
"In fact when I said by default an assignment operator is made available for the class, I was overly simplifying the issue. Let's consider for instance a class with a reference data attribute:"
"In fact when we said by default an assignment operator is made available for the class, we were overly simplifying the issue. Let's consider for instance a class with a reference data attribute:"
]
},
{
...
...
@@ -432,7 +504,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### The dangers of copy constructions... and how I avoid them\n",
"### The dangers of copy constructions... and how I (Sébastien) avoid them\n",
"\n",
"Copy construction may in fact be quite dangerous:\n",
"\n",
...
...
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Assignment operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb)
%% Cell type:markdown id: tags:
## Assignment operator
We have not yet addressed one of the most natural operator: the one that might be used to allocate a value to an object.
### Default behaviour (for a simple case)
This one is provided by default:
%% Cell type:code id: tags:
``` c++
%%cpptoolboxcppyy/cppdef
#include<iostream>
classVector
{
public:
Vector(doublex,doubley,doublez);
Vector&operator=(constVector&)=default;
voidPrint(std::ostream&out)const;
private:
doublex_=0.;
doubley_=0.;
doublez_=0.;
doublex_{};
doubley_{};
doublez_{};
};
```
%% Cell type:code id: tags:
``` c++
%%cpptoolboxcppyy/cppdef
Vector::Vector(doublex,doubley,doublez)
:x_(x),
y_(y),
z_(z)
:x_{x},
y_{y},
z_{z}
{}
```
%% Cell type:code id: tags:
``` c++
voidVector::Print(std::ostream&out)const
{
out<<"("<<x_<<", "<<y_<<", "<<z_<<")";
}
```
%% Cell type:code id: tags:
``` c++
{
Vectorv1(3.,5.,7.);
Vectorv2(-4.,-16.,0.);
v2=v1;
v2.Print(std::cout);
}
```
%% Cell type:markdown id: tags:
### The pointer case
So end of the story? Not exactly... Let's write the same and store the values in a dynamic array instead (of course you shouldn't do that in a real case: `std::vector` or `std::array` would avoid the hassle below!):
deletev2;// WARNING: Comment this line for not kernel to crash
}
```
%% Cell type:markdown id: tags:
At the time of this writing, this makes the kernel crash... In a more advanced environment ([Wandbox](https://wandbox.org) for instance) the reason appears more clearly:
At the time of this writing, this makes the kernel crash... In a more realistic environment (below when the code is compiled with clang and executed, but you can play with [Wandbox](https://wandbox.org) if you want to see how the different compilers handle it) the reason appears more clearly:
%% Cell type:markdown id: tags:
%% Cell type:code id: tags:
``` c++
%%cpptoolboxclang
// Exact same code as abovce, but given to your clang++ compiler to be compiled and executed
#include<iostream>
classVector2
{
public:
Vector2(doublex,doubley,doublez);
~Vector2();
voidPrint(std::ostream&out)const;
```txt
** Error in `./prog.exe': double free or corruption (fasttop): 0x0000000001532da0 **
So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice.
One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!):
%% Cell type:code id: tags:
``` c++
%%cpptoolboxcppyy/cppdef
#include<iostream>
classVector3
{
public:
Vector3(doublex,doubley,doublez);
~Vector3();
// Then again the Xeus-cling issue with out of class operator definition.
Vector3&operator=(constVector3&rhs)
{
// Array already initialized in constructor; just change its content.
for(autoi=0ul;i<3ul;++i)
array_[i]=rhs.array_[i];
return*this;// The (logical) return value for such a method.
// Array already initialized in constructor; just change its content.
for(autoi=0ul;i<3ul;++i)
array_[i]=rhs.array_[i];
return*this;// The (logical) return value for such a method.
}
```
%% Cell type:code id: tags:
``` c++
{
Vector3v1(3.,5.,7.);
Vector3v2(-4.,-16.,0.);
v2=v1;
v1.Print(std::cout);
std::cout<<std::endl;
v2.Print(std::cout);
}
```
%% Cell type:markdown id: tags:
### Uncopyable class
In fact when I said by default an assignment operator is made available for the class, I was overly simplifying the issue. Let's consider for instance a class with a reference data attribute:
In fact when we said by default an assignment operator is made available for the class, we were overly simplifying the issue. Let's consider for instance a class with a reference data attribute:
%% Cell type:code id: tags:
``` c++
classClassWithRef
{
public:
ClassWithRef(int&index);
private:
int&index_;
};
```
%% Cell type:code id: tags:
``` c++
ClassWithRef::ClassWithRef(int&index)
:index_(index)
{}
```
%% Cell type:code id: tags:
``` c++
{
inta=5;
ClassWithRefobj(a);
}
```
%% Cell type:code id: tags:
``` c++
{
inta=5;
intb=7;
ClassWithRefobj1(a);
ClassWithRefobj2(b);
obj2=obj1;// COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
A class with a reference can't be copied: the reference is to be set at construction, and be left unchanged later. So a copy is out of the question, hence the compilation error.
The same is true as well for a class with an uncopyable data attribute: the class is then automatically uncopyable as well.
%% Cell type:markdown id: tags:
### Copy constructor
Please notice however that it is still possible to allocate a new object which would be a copy of another one with a **copy constructor**. The syntax is given below:
%% Cell type:code id: tags:
``` c++
classClassWithRef2
{
public:
ClassWithRef2(int&index);
ClassWithRef2(constClassWithRef2&)=default;
private:
int&index_;
};
```
%% Cell type:code id: tags:
``` c++
ClassWithRef2::ClassWithRef2(int&index)
:index_(index)
{}
```
%% Cell type:code id: tags:
``` c++
{
inta=5;
ClassWithRef2obj1(a);
ClassWithRef2obj2(obj1);// ok
ClassWithRef2obj3{obj1};// ok
}
```
%% Cell type:markdown id: tags:
There are effectively two ways to copy an object:
* With an assignment operator.
* With a copy constructor.
It is NOT the same underlying method which is called under the hood, you might have a different behaviour depending on which one is called or not!
%% Cell type:markdown id: tags:
### The dangers of copy constructions... and how I avoid them
### The dangers of copy constructions... and how I (Sébastien) avoid them
Copy construction may in fact be quite dangerous:
* As we've just seen, assignment and construction may differ in implementation, which is not a good thing. There are ways to define one in function of the other (see for instance item 11 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)) but they aren't that trivial.
* Depending on somewhat complicated rules that have evolved with standard versions, some might or might not be defined implicitly.
* More importantly, assignment operators may be a nightmare to maintain. Imagine you have a class for which you overload manually the assignment operator and/or the copy constructor. If later you add a new data attribute, you have to make sure not to forget to add it in both implementations; if you forget once you will enter the realm of undefined behaviour... and good luck for you to find the origin of the bug!
To avoid that I took the extreme rule to (almost) never overload those myself:
* As explained briefly above, using appropriate container may remove the need. `Vector3` could be written with a `std::array` instead of the dynamic array, and the STL object will be properly copied with default behaviour!
* I define explicitly in my classes the behaviour of these operators with `= default` or `= delete` syntaxes. More often than not, my objects have no business being copied and `= delete` is really my default choice (this keyword indicates to the compiler the operator should not be provided for the class).
I don't pretend it is the universal choice, just my way to avoid the potential issues with manually overloaded copy operators (some would say the [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) would avoid the most problematic one, but in the real world I find it difficult to stick with this principle...)
%% Cell type:markdown id: tags:
## Canonical form of a class
So a typical class of mine looks like:
%% Cell type:code id: tags:
``` c++
classAlmostCanonicalClass
{
public:// or even protected or private for some of them!
Your IDE or a script might even be handy to generate this by default (with additional Doxygen comments for good measure).
%% Cell type:markdown id: tags:
### [Advanced] The true canonical class
Why _almost_ canonical class? Because C++ 11 introduced a very powerful **move semantics** (see the [notebook](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) about it) and so the true canonical class is:
%% Cell type:code id: tags:
``` c++
classTrueCanonicalClass
{
public:// or even protected or private for some of them!
In my programs, I like to declare explicitly all of them, using `default` and `delete` to provide automatic implementation for most of them.
I admit it's a bit of boilerplate (and to be honest a script does the job for me in my project...), if you don't want to there are in fact rules that specify which of them you need to define: for instance if a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three. See [this cppreference link](https://en.cppreference.com/w/cpp/language/rule_of_three) for more about these rules and [this blog post](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/) for an opposite point of view.
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb)
%% Cell type:markdown id: tags:
## What is a functor?
### Disclaimer: not a functional programming functor!
First of call, for those of you with a background in functional programming, what C++ calls a `functor` is [not what you are used to](https://en.wikipedia.org/wiki/Functor_(functional_programming)).
### Functor in C++
We can provide a class with an `operator()` that allows to give its objects a function behavior. In other words: if you write an expression in which an object is used as if it were a function, it is its execution operator that is invoked.
We can also see these *functional objects*, or *functors*, as functions to which we would have added state parameters from one call to another.
The return type and the arguments are not constrained and might be defined as you wish in the class (`int` is used here for return type, and an `int` is required as argument):
%% Cell type:code id: tags:
``` c++
classLinearFunction
{
public:
LinearFunction(intconstant);
intoperator()(intvalue)
{
returnconstant_*value;// here due to usual Xeus-cling issue when out of class
This might seem inconsequential, but they are sometimes extremely useful.
## Functors in STL
STL itself defines some functors: imagine you want to sort a `std::vector` decreasingly; you can't directly put `>` as the comparison operation in `sort` and therefore may use a specific STL-defined `greater`:
C++ 11 and above limits the need for functors with lambda functions (see [earlier](../1-ProceduralProgramming/4-Functions.ipynb#Lambda-functions)), but in older codes, functors were one of the way to pass your own rules to some STL algorithms, along with pointer to functions.