Mentions légales du service

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • v19.05
  • v20.03
  • v21.05
  • v22.10
5 results

Target

Select target project
  • formations/cpp/gettingstartedwithmoderncpp
  • sbenamor/gettingstartedwithmoderncpp
  • steff/gettingstartedwithmoderncpp
  • sgilles/gettingstartedwithmoderncpp
  • vrouvrea/gettingstartedwithmoderncpp
  • fvergnet/gettingstartedwithmoderncpp
  • jediaz/gettingstartedwithmoderncpp
  • mmalanda/gettingstartedwithmoderncpp
  • bnguyenv/gettingstartedwithmoderncpp
9 results
Select Git revision
  • 112_binder
  • 112_binder_support
  • 112_mr
  • 113_jupytext
  • 113_jupytext_dot_binder
  • 113_jupytext_postbuild
  • 113_jupytext_postbuild_pyproject
  • 124_decl_def
  • 124_test_compilation_ok
  • 126_zotero
  • 129_rephrasing
  • 134_virtual_calls
  • algorithm_rereading
  • ci_nbstripout
  • cppyy
  • develop
  • hands_on
  • jupytext
  • jupytext_in_postbuild
  • jupytext_pyproject_mac
  • master
  • miscellaneous_small_fixes
  • object_rereading
  • procedural_rereading
  • rereading_operators
  • rereading_template_notebooks
  • sebastien_note_2024_day2
  • rc24.03.4
  • rc24.03.5
  • rc24.03.6
  • rc24.03.7
31 results
Show changes
Commits on Source (77)
Showing
with 837 additions and 436 deletions
......@@ -43,11 +43,6 @@ check_notebooks_empty_outputs:
- changes:
- docker/Dockerfile.${NAME}
build_xeus-cling:
extends: .build_docker_image
variables:
NAME: "xeus-cling"
build_fedora:
extends: .build_docker_image
variables:
......@@ -58,6 +53,7 @@ build_fedora_with_boost:
variables:
NAME: "fedora_with_boost"
compile_hands_on_solutions:
image: $CI_REGISTRY/$CI_PROJECT_PATH/fedora_for_hands_on
stage: check
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Getting started with the tutorial](./getting_started_with_tutorial.ipynb)
%% Cell type:markdown id: tags:
## About the choice of a Jupyter notebook
## Jupyter notebook
This notebook uses up [xeus-cling](https://xeus-cling.readthedocs.io/en/latest/), a special instance of Jupyter able to run C++ code based upon xeus (tool to build Jupyter kernels for any language) and cling (a creation from CERN to be able to run C++ as an interpreted language).
### About the choice of a Jupyter notebook
The reasons for these choices is to really access directly to handle C++ code without the hassle of explaining how to compile and run stuff, which is an especially cumbersome way to start with this (or any really...) language.
We made the choice to use a Jupyter notebook for conveniency:
This is not to say this tutorial will ignore entirely these topics (see the dedicated [chapter](./6-InRealEnvironment/0-main.ipynb)), just that we will first focus on C++ code. However keep in mind that this notebook's fancy interpreter is not a typical C++ environment.
- We can delve directly into the syntax of the language itself, without muddying the water with peripherical stuff such as explaining from the beginning how a build system works.
(don't worry we'll cover real environments [in part 6](./6-InRealEnvironment/0-main.ipynb)
- Notebooks are a cool tool to meddle explanations and actual code that is directly executable.
## When the notebook is not enough...
However, Jupyter notebooks were clearly not initially designed with C++ in mind (Jupyter stands for **Ju**Lia **Py**thon and **R** - these three langages are either interpreted or just-in-time, whereas C++ is a compiled langage).
As we shall see repeatedly, Xeus-cling notebooks are far from being full-proof: some stuff that are perfectly acceptable C++ aren't accepted in them, and some others required work-arounds. When such an issue appears:
This didn't stop people from trying, leveraging the [cling](https://root.cern/cling) project from CERN which aims to provide an interpreter to the C++ langage.
* It will be indicated explicitly in the notebook if a specific work around is used. We do not want you to take Jupyter work-arounds as a legit advice on how to write proper C++.
* If Jupyter can't deal with the code, we will use [Coliru](https://coliru.stacked-crooked.com/). Coliru is a C++ online compiler; others are listed [here]([GitHub page](https://arnemertz.github.io/online-compilers/)) ([Wandbox](https://wandbox.org/) deserves a shout out as it enables testing the same code with a great variety of compiler versions).
Up to summer 2024, we were using [Xeus-cling](https://xeus-cling.readthedocs.io/en/latest) to run this tutorial; however this project was stagnating and in particular doesn't seem keen to adopt C++ 20, which is (partially) supported by the cling interpreter.
We're not sure we'll keep using Jupyter notebooks in the future: development of Xeus-cling has a bit stalled in the recent years and support of more recent versions of C++ (20 and more) is still unclear.
We have therefore switched to a [homemade Jupyter kernel](https://gitlab.inria.fr/sed-saclay/cppyy_kernel) which uses up the fantastic [cppyy](https://cppyy.readthedocs.io) project, which enables running C++ code from a Python environment.
That being said, it is important to keep in mind that this notebook's fancy interpreter is absolutely not a typical C++ environment.
### When cling / the notebook is not enough...
Even if notebooks are really useful, there are some C++ operations that are not fully supported, be it due to cling limitations (either intrinsic or just because some new C++ features aren't yet covered) or to the way we implemented our kernel around cppyy.
We try our best to make the most content available directly, and we designed our kernel accordingly. We used so-called *magics* to do so, so don't be surprised if some cells starts with a line such as:
```jupyter
%%cppmagics cppyy/cppdef
```
Such lines are dedicated to the running of the tutorial and have nothing to do with C++ itself; you may entirely ignore it (unless of course you intend to use our cppyy kernel for your own purposes).
The most common such magics are:
- `%%cppmagics cppyy/cppdef`: cppyy in fact defines two different functions `cppexec` and `cppdef`. The former is for code deemed to be executed, the latter for code that defines classes, functions and so on. In our Jupyter kernel we use by default `cppexec`, which works just fine for most operations. However some really need to call under the hood `cppdef`, and that's the reason for this magics.
- `%%cppmagics clang`: this magics tells that the entire content of the cell is to be written into a file that is to be compiled by `clang++` compiler; the executable hence produced is then run directly and its output is printed in the notebook. Contrary to the usual behaviour, the content of the cell is sandboxed and self-contained.
The list of all magics may be displayed with following cell:
%% Cell type:code id: tags:
``` c++
%%cppmagics
;
```
%% Cell type:markdown id: tags:
## Few guidelines about Jupyter
### Few guidelines about Jupyter
You might not be familiar with Jupyter notebooks, so here are few tips to run it smoothly (the _Help_ menu will help you find more if you need it).
In a Jupyter notebook the content is divided into _cells_, in our case we are using two kind of cells:
* Markdown cells, such as the ones into these very words are written.
* Code cells, which are running code. In these notebooks the chosen kernel is C++17, so the code is C++17 which is interpreted by cling.
There are two modes:
* Edit mode, in which you might change the content of a cell. In this mode the left part of the cell is in green.
* Command mode, in which you might take actions such as changing the type of a cell, create or delete a new one, etc...
To enter in edit mode, simply type on 'Enter'.
To enter in command mode, type 'Esc'.
To execute a cell, type 'Shift + Enter'. For a markdown cell it will edit nicely the content by interpreting the markdown, and for a code cell it will run the code.
In command mode, several handy shortcuts are there; I would recommend especially:
* `a` (add a cell above)
* `b` (add a cell below)
* `x` (cut a cell)
* `M` (change cell mode to Markdown)
The complete list is available in _Help_ > _Keyboard_ shortcut.
If for some reason the code in the notebook seems stuck, you might try to restart the kernel with one of the restart option in the _Kernel_ menu.
### Restarting the kernel
#### Restarting the kernel
Sometimes something that should work doesn't... In this case try restarting the kernel: it might fix your issue!
### Table of contents
#### Table of contents
The table of content for a given notebook is available as a side panel if you go to _View_ > _Table of contents_ or if you click on the third item on the leftmost panel.
%% Cell type:markdown id: tags:
## Very basic C++ syntax (in notebook and in general)
### Semicolons
In C++ most instructions end by a semicolon `;`. If you forget it, the underlying compiler doesn't understand the syntax.
%% Cell type:code id: tags:
``` C++17
``` c++
{
int foo = 5 // COMPILATION ERROR!
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
int foo = 5; // OK
}
```
%% Cell type:markdown id: tags:
Spaces, end lines and tabulations act as word separators; utterly unreadable code as the one below is perfectly fine from the compiler standpoint:
%% Cell type:code id: tags:
``` C++17
``` c++
# include <string>
{
int number ; number = 1
; std::string name;
name=
"truc" ;
number = 2
;
}
```
%% Cell type:markdown id: tags:
### Input / output
Inputs and outputs aren't directly a part of the language itself, but are in the standard library (often abbreviated as STL for *Standard Template Library* even if some purist may yell and explain it's not 100 % the same thing...). You therefore need to __include__ a file named `iostream`; doing so will enable the use of the input / output facilities.
%% Cell type:code id: tags:
``` C++17
``` c++
{
std::cout << "Hello world!" << std::endl; // Should fail (unless you run a cell that includes iostream before)
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
std::cout << "Hello world!" << std::endl; // Should work: std::cout and std::endl are now known.
}
```
%% Cell type:markdown id: tags:
- `std::cout` is the symbol to designate the standard output (i.e. your screen...)
- `std::endl` is the symbol to clean-up the stream and go to next line.
The operator `<<` is used to indicate what you direct toward the stream; here `std::cout << "Hello world!"` tells to redirect the string toward the standard output.
We will see that a bit more in detail in [a later chapter](./1-ProceduralProgramming/6-Streams.ipynb), but printing something is really helpful early on hence this brief introduction here.
%% Cell type:markdown id: tags:
### Comments
There are two ways to comment code in C++:
- `//` which comments all that is after this symbol on the same line.
- `/*` ... `*/` which comments everything between the symbols.
%% Cell type:code id: tags:
``` C++17
``` c++
{
int i = 0; // Everything after // is commented until the end of the line
/*
commented...
also commented...
*/
int j = 5; // no longer commented
/*
// This type of comment might be used inside the other style
*/
}
```
%% Cell type:markdown id: tags:
[© Copyright](COPYRIGHT.md)
......
......@@ -35,16 +35,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./1-Variables.ipynb)
%% Cell type:markdown id: tags:
## Ordinary variables
%% Cell type:markdown id: tags:
### Declaration
%% Cell type:markdown id: tags:
To be usable in a C++ program, a variable must be declared. This declaration shall include at least the type of
the variable, followed by its name and a semicolon.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int number; // integer variable
double real; // floating-point variable
// C++ standard dictates nothing - don't expect 0 for all architectures even if you get one here!
std::cout << number << std::endl;
std::cout << real << std::endl;
}
```
%% Cell type:markdown id: tags:
### Initialisation
%% Cell type:markdown id: tags:
Although not mandatory, it is **strongly** recommended to give an
initial value to your variables, as an expression between brackets.
Not providing an initial value may lead to unexpected behaviour. For instance you can't make hypothesis upon the values of `number` and `real` in the cell above: you might end-up with any value... and someone else might get other values on their computer!
If you give braces without values, a predefined and associated value
to the type is used (usually a form of 0).
%% Cell type:code id: tags:
``` C++17
``` c++
{
int nb1 { 1 }; // integer variable set with the value 1
int nb2 {}; // same as int nb2{0};
double pi { 3.14 }; // real variable
}
```
%% Cell type:markdown id: tags:
C++ actually supports many other historical forms
of initialization, which you will encounter everywhere, including in this tutorial,
with brackets and/or an equal sign. There are some subtle differences
between each other... that you can ignore most of the time (still, if you're beginning our advice is to take the habit to use braces which are both less ambiguous and a bit more secure than the others syntaxes).
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a = 5;
int b(5);
int c { 5 }; // From C++ 11 onward; we advise you to use this syntax
}
```
%% Cell type:markdown id: tags:
In all cases, even if there is an equal sign, it is important to remember
that it is an initialization, not an assignment (this will be
important when we will define our own types).
%% Cell type:markdown id: tags:
### Assignment
A new value is stored in an existing variable using the assignment
operator `=`. The name of the variable is on the left; the expression
on the right of the `=` sign is evaluated, and its result is assigned to the variable.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream> // for std::cout and std::endl
{
int a {}, b {}, c {} ; // default initialization; set the values to 0
std::cout << "Default initialization: a = " << a << ", b = " << b << " and c = " << c << std::endl;
a = 4;
b = 7;
c = a + b;
std::cout << "After assignments: a = " << a << ", b = " << b << " and c = " << c << std::endl;
}
```
%% Cell type:markdown id: tags:
Assignments may be chained:
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a {}, b {}, c {};
a = b = c = 5;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
}
```
%% Cell type:markdown id: tags:
It is also possible to define (slightly) more advanced operators of assignments that modify the value currently stored by a simple operation:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a {}, b {}, c {} ; // default initialization; set the values to 0
a += 4; // add 4 to the current value of 'a'
std::cout << "a = " << a << std::endl;
a *= 7; // multiply current value of 'a' by 7
std::cout << "a = " << a << std::endl;
a /= 9; // divide 'a' by 9 and assign the quotient to 'a'
std::cout << "a = " << a << std::endl;
}
```
%% Cell type:markdown id: tags:
### Scope and blocks
A variable is destroyed when it becomes **out of scope**.
The **scope** of a variable is its lifetime: it begins when the variable is declared and ends when it is destroyed, usually when we reach the end of the block where it is defined.
A **block** is essentially what is between braces `{}`.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a = 5;
{
std::cout << "a is available at this level: " << a << std::endl;
{
int b = 10;
std::cout << "b is available at this level: " << b << std::endl;
std::cout << "and a is also still available: " << a << std::endl;
} // b becomes out of scope
std::cout << "a is available at this level: " << a << "... but b is not!" << std::endl;
}
```
%% Cell type:markdown id: tags:
Within a same block, a variable name may be only used once:
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a = 5;
std::cout << "Value = " << a << std::endl;
int a = 1; // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
**Beware:** it is however entirely possible to reuse in an inner block a variable name... but it is to be avoided because it really clutters the understanding of the code for a reader! (and compilers usually warn against this, even if here Xeus-cling does not):
**Beware:** it is however entirely possible to reuse in an inner block a variable name... but it is to be avoided because it really clutters the understanding of the code for a reader!
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a = 5;
{
int a = 10;
std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl;
{
std::cout << "Of course same here: " << a << std::endl;
}
a = a + 5; // this is the inner most 'a' that is modified
} // inner most a becomes out if scope
std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' "
"is the initial one: " << a << std::endl;
```
%% Cell type:markdown id: tags:
The special compiler used in our kernel environment does not warn about the repetition of the variable name, but a real-life compiler worth its salt would:
%% Cell type:code id: tags:
``` c++
%%cppmagics clang
// < We're asking here to the system clang compiler to compile and execute the code.
// Don't bother for the time being for the supplementary lines: we'll cover them in subsequent notebooks.
#include <cstddef>
#include <iostream>
int main(int argc, char** argv)
{
int a = 5;
{
int a = 10;
std::cout << "a is available at this level; it is the most inner scope one: " << a << std::endl;
{
std::cout << "Of course same here: " << a << std::endl;
}
a = a + 5; // this is the inner most 'a' that is modified
} // inner most a becomes out if scope
std::cout << "Here the innermost 'a' got out of scope and therefore the best choice for 'a' "
"is the initial one: " << a << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
As seen above, you can declare a block very easily by firing up opening and closing braces. This is an incredibly useful feature: you may thus **fine tune the lifetime of your variable** to ensure a variable is not used past its prime.
%% Cell type:markdown id: tags:
### Increment and decrement operators
Finally, C++ also provides a shortcut when value is either incremented or decremented by adding `++` or `--` before or after the name of the variable.
* If the sign is placed before the variable, it is a **pre-increment**.
* If the sign is placed after the variable, it is a **post-increment**.
An example is the better way to explain the difference between both:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a = 5;
int b = 3;
a++; // increment a by 1.
++a; // same, both are actually equivalents here.
int c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
c = a-- + b; // first a + b is evaluated, and only then a is decremented.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
c = a + ++b; // first b is incremented, and only then a + b is evaluated.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:markdown id: tags:
Honestly it's usually better to remove any ambiguity by separating explicitly both operations:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a = 7;
int b = 3;
int c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
c = a + b;
--a; // equivalent to a-- but for reasons related to the standard library I advise you
// to rather use the pre-increment form when both are equivalent.
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
++b; // same: equivalent to b++;
c = a + b;
std::cout << "a = " << a << ", b = " << b << " and c = " << c << std::endl;
}
```
%% Cell type:markdown id: tags:
### Comparing values
As shown above, `=` is the assignment operator. To compare two values, the symbol to use is `==`.
Other comparison operators are:
| Operator | Effect |
|:------------- |:-------------------------------------------:|
| `a == b` | `true` if a and b are equals |
| `a != b` | `true` if a and b are different |
| `a < b` | `true` if a is less than b |
| `a > b` | `true` if a is greater than b |
| `a >= b` | `true` if a is greater than or equal to b |
| `a <= b` | `true` if a is less than or equal to b |
These operators are defined for most ordinary types and may be defined for your own types (we'll see that [later](../3-Operators/2-Comparison.ipynb)).
%% Cell type:markdown id: tags:
## References
A reference is a variable that acts as a kind of alias, and provides another name for the same variable.
When defining a reference, it must be **immediately** initialized by
indicating to which variable it should point; it cannot be changed after that.
The syntax is to add a `&` character just after the type:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a { 2 };
int b { a };
int& c { a }; // c is a reference to a
std::cout << "Initial values : a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
a = -7;
std::cout << "Modify a : a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
b = 42;
std::cout << "Modify b : a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
c = 0;
std::cout << "Modify c : a = " << a << ", b = " << b << " and c = " << c << std::endl;
```
%% Cell type:markdown id: tags:
Reference is a purely C++ concept that doesn't exist in C.
%% Cell type:markdown id: tags:
## Pointers
A pointer contains the address in memory of another variable. It declares itself by slipping
a `*` character before the name. It can be initialized or not with the address
of another variable. To explicitly extract the address of this other variable,
we use the symbol `&`.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a { 2 };
int* p {&a}; // define a pointer p which is initialized with the address of a
std::cout << "a = " << a << std::endl;
std::cout << "p = " << p << std::endl;
```
%% Cell type:markdown id: tags:
You may ask the underlying value at a given address with `*` (I reckon: this syntax may be _very_ confusing at first...)
%% Cell type:code id: tags:
``` C++17
``` c++
std::cout << "Value stored at p = " << *p << std::endl;
```
%% Cell type:markdown id: tags:
The `*` syntax may be used to modify the underlying value:
%% Cell type:code id: tags:
``` C++17
``` c++
*p = *p + 5;
std::cout << "After the operation: pointer " << p << " stores the value " << *p << std::endl;
```
%% Cell type:markdown id: tags:
Pointers may be used as variables by themselves and see their actual content changed during execution of a program:
%% Cell type:code id: tags:
``` C++17
``` c++
int b { 3 };
p = &b;
std::cout << "After the pointer assignation: pointer " << p << " stores the value " << *p << std::endl;
std::cout << "(and value of a remains unchanged: " << a << ')' << std::endl;
```
%% Cell type:markdown id: tags:
We can therefore see the pointer as a kind of redefinable reference (pointers are a feature from C language whereas references are a C++ specific feature).
%% Cell type:markdown id: tags:
### Cheatsheet: pointers and reference syntax
%% Cell type:markdown id: tags:
| Applied to: | A type `T` | A variable `x`
|:------------- |:-------------------------------------------:|:-------------------------------------------:|
| * | Pointer to an object of type `T` | Variable under `x` if `x` is a pointer, invalid code otherwise |
| & | Reference to an object of type `T` | Address of the variable (i.e. a pointer) |
%% Cell type:markdown id: tags:
### Chaining pointers
It is possible to chain pointers:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int n { 5 };
int* p { &n };
int** q { &p };
std::cout << "*q is a pointer to an int (i.e. an address): " << *q << " - it is the same as what is stored in p: " << p << std::endl;
std::cout << "**q gives the original `n` value: " << **q << std::endl;
}
```
%% Cell type:markdown id: tags:
This was something that was very common in C, but that I do not recommend in C++ as there are much better ways to achieve the same goals (that we do not have seen yet, so don't panic if you do not understand why we would need this).
%% Cell type:markdown id: tags:
### `nullptr`
A pointer can also designate no variables if initialized with the special value
`nullptr`. It is then a mistake to try to access its pointed value (as we shall see later, an [`assert`](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) is a good idea to ensure we do not try to dereference a `nullptr` value).
It is strongly recommended to initialize a pointer when creating it: if you define an uninitialized pointer, it points to an arbitrary area of memory, which can create undefined behaviors that are not necessarily reproducible.
If the pointed area is known at initialization and never changes throughout the program, you should consider a reference rather than a pointer.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int* p { nullptr }; // define a null pointer p
std::cout << "\t p: address = " << p << ", value = " << *p << std::endl; // Dereferencing p is misguided!
}
```
%% Cell type:markdown id: tags:
`nullptr` was introduced in C++ 11; if you're working with a legacy codebase that doesn't use this standard use `NULL` (but stick to `nullptr` for modern code!)
%% Cell type:markdown id: tags:
## Constant variables and pointers
When declaring a variable of a predefined type, it is possible to specify its value can't be changed afterward by using the word `const` which may be placed before or after the type:
%% Cell type:code id: tags:
``` C++17
``` c++
const double pi { 3.1415927 };
double const pi_2 { 3.1415927 }; // equally valid; it is just a matter of taste. Mine is to put it before,
// so that is what you will see in the remaining of the lecture.
// There are however very persuasive arguments for the other convention:
// see http://slashslash.info/eastconst/
```
%% Cell type:code id: tags:
``` C++17
``` c++
pi = 5.; // COMPILATION ERROR!
```
%% Cell type:code id: tags:
``` C++17
``` c++
pi_2 = 7.; // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
In the case of a pointer, we can declare the pointer itself constant, and/or the value pointed to depending on whether we place the keyword `const` before or after the type name (in this case it applies to the pointed value) or after the character `*` (in this case it applies to the pointer itself):
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a { 2 }, b { 3 };
int* p { &a }; // Both pointer and pointed values are modifiable.
p = &b; // OK
*p = 5; // OK
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
int a { 2 }, b { 3 };
int* const p { &a }; // Value is modifiable, but not the address pointed to.
*p = 5; // OK
```
%% Cell type:code id: tags:
``` C++17
``` c++
p = &b; // COMPILATION ERROR
```
%% Cell type:code id: tags:
``` C++17
``` c++
const int* p { &a }; // Address pointed to is modifiable, but not the underlying value.
p = &b; // OK
```
%% Cell type:code id: tags:
``` C++17
``` c++
*p = 5; // COMPILATION ERROR
```
%% Cell type:code id: tags:
``` C++17
``` c++
int a { 2 }, b { 3 };
const int* const p { &a }; // Nothing is modifiable
p = &b; // COMPILATION ERROR
```
%% Cell type:code id: tags:
``` C++17
``` c++
*p = 5; // COMPILATION ERROR
```
%% Cell type:markdown id: tags:
**IMPORTANT**: Even if declared `const`, the pointed value is
not intrinsically constant. It just can't be
modified through this precise pointer.
If other variables reference the same memory area and
are not constant, they are able to modify the value:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { 2 }, b { 3 };
const int* p { &a }; // Address pointed to is modifiable, but not the underlying value.
std::cout << "Value pointed by pointer p (which doesn't allow value modification) is: " << *p << std::endl;
int* p2 {&a}; // Pointer to the same memory area, but no constness here.
*p2 = 10;
std::cout << "Value pointed by pointer p (and modified through p2) is: " << *p << std::endl;
a = -3;
std::cout << "Value pointed by pointer p (and modified through variable directly) is: " << *p << std::endl;
}
```
%% Cell type:markdown id: tags:
On the other hand, pointers can't be used as a work-around to modify a constant value:
%% Cell type:code id: tags:
``` C++17
``` c++
const int n { 3 };
```
%% Cell type:code id: tags:
``` C++17
``` c++
int* p { &n }; // COMPILATION ERROR
```
%% Cell type:code id: tags:
``` C++17
``` c++
const int* p_n2 { &n }; // OK
```
%% Cell type:markdown id: tags:
## Arrays
The operator `[]` enables the creation of an array.
**Beware:** In C++, the indexes of an array start at 0.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i[10] ; // Array of 10 integers - not initialised properly!
double x[3] = { 1., 2., 3. }; // Array of 3 reals, C++ 11 syntax
std::cout << "i[2] = " << i[2] << " (may be gibberish: undefined behaviour due to lack of initialization!)" << std::endl;
std::cout << "i[10] = " << i[10] << " (undefined behaviour: out of range. Warning identifies the issue)" << std::endl ;
std::cout << "x[1] = " << x[1] << " (expected: 2.)" << std::endl ;
}
```
%% Cell type:markdown id: tags:
Multi-dimensional arrays are also possible:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int k[2][3] = { { 5, 7, 0 }, { 3, 8, 9 } };
std::cout << "k[0][0] = " << k[0][0] << " (expected: 5)" << std::endl;
std::cout << "k[1][2] = " << k[1][2] << " (expected: 9)" << std::endl;
int l[2][3] = {}; // default initialization of all elements of the array.
std::cout << "l[0][0] = " << l[0][0] << " (expected: 0)" << std::endl;
std::cout << "l[1][2] = " << l[1][2] << " (expected: 0)" << std::endl;
}
```
%% Cell type:markdown id: tags:
**IMPORTANT**: so far we have considered only the case of _static_ arrays, for which the size is already known at compilation. We will deal with dynamic ones [later in this tutorial](./5-DynamicAllocation.ipynb#Arrays-on-heap) (and also with the standard libraries [alternatives](../5-UsefulConceptsAndSTL/3-Containers.ipynb) such as `std::vector` or `std::array` which are actually much more compelling).
%% Cell type:markdown id: tags:
### Arrays and pointers
The variable designating an array is similar to a constant pointer pointing to the beginning of the array. Increasing this pointer is like moving around in the array.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int i[2] = { 10, 20 } ;
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::cout << *i << " (expected: 10)" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::cout << *i + 1 << " (expected: 11)" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::cout << *(i + 1) << " (expected: 20)" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
int* j = i; // OK!
++j; // OK!
std::cout << "Value pointed by j is " << *j << " (expected: 20)" << std::endl;
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Conditions and loops](./2-Conditions-and-loops.ipynb)
%% Cell type:markdown id: tags:
## Conditions
%% Cell type:markdown id: tags:
### `if` condition followed by a single statement
%% Cell type:markdown id: tags:
In C++, a condition is the `if` command followed by a condition in parenthesis `()`. The single instruction (that ends at the next `;`) _or_ the block (in braces `{}`) that follows is then executed only if the condition is true.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { 2 };
if (a > 0)
std::cout << a << " is greater than 0" << std::endl;
if (a < 0)
std::cout << "This line won't be executed so nothing will be printed." << std::endl;
std::cout << "But this line will be printed: without braces `{}` only the first instruction depends "
"on the condition!" << std::endl;
}
```
%% Cell type:markdown id: tags:
Of course, the precedent code is embarrassing but is due to the poor indenting used: you have to remember that in C++ indenting is just for the programmer: it is much easier to read if a program is properly indented, but it doesn't matter from the compiler standpoint!
Our case above would be much clearer however with a more logical indenting and spacing:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { 2 };
if (a < 0)
std::cout << "This line won't be executed so nothing will be printed." << std::endl;
std::cout << "It's much clearer now that this line is not encompassed by the condition!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### `if` condition followed by a block
And if you need several lines to be encompassed by the condition, just use braces!
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { 2 };
if (a < 0)
{
std::cout << "Should not be printed: the condition is false!" << std::endl;
++a; // won't be executed: the condition is false
}
std::cout << "a was not modified and is still 2: " << a << std::endl;
}
```
%% Cell type:markdown id: tags:
### No semicolon at the end of the `if` condition
__BEWARE__: do not put a `;` at the end of an `if` statement! If you do so, the statement executed if the condition is true is the empty statement `;` which does nothing... The risk is rather mitigated: any compiler worth its salt will warn you if you do this mistake.
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a { 2 };
if (a == 0);
std::cout << "Will be printed: the statement after the condition is ';', which does nothing..." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
int a { 2 };
if (a == 2)
; // putting the semicolon in a different line silences the warning; there are legitimate cases in
// which it's useful to do so and the risk is
// slim that this was written by mistake.
}
```
%% Cell type:markdown id: tags:
### if ... else if ... else
It is rather usual to foreseen two (or more...) courses of action depending on the results of one or several conditions. The syntax in this case is the following:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { 2 };
if (a < 0)
std::cout << a << " is negative." << std::endl;
else if (a == 0)
{
std::cout << a << " is zero." << std::endl;
}
else if (a < 10)
std::cout << a << " is positive but lower than 10." << std::endl;
else if (a < 20)
std::cout << a << " is in the interval [10, 20[." << std::endl;
else
{
std::cout << a << "is greater than 20." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
Please notice that:
* `else if` and `else` syntax is the same as the one for `if` and you may choose a single statement or a block in each branch of the condition.
* As soon as one condition is fulfilled, the execution of the condition blocks ends. I therefore didn't have to spell out in the (`a < 20`) case that `a` had to be greater than 10.
%% Cell type:markdown id: tags:
### The ternary operator
%% Cell type:markdown id: tags:
C++ has inherited from C the so-called ternary operator, which is basically an if/else case packed in one line:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i { 5 };
std::cout << "Is " << i << " even? -> " << (i % 2 == 0 ? "true" : "false") << std::endl;
}
```
%% Cell type:markdown id: tags:
It is really the same as:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i { 5 };
std::cout << "Is " << i << " even? -> ";
if (i % 2 == 0)
std::cout << "true" << std::endl;
else
std::cout << "false" << std::endl;
}
```
%% Cell type:markdown id: tags:
There are nonetheless some cases in which a ternary operator performs a task that is not otherwise reachable by an if/else statement; please consider for instance the initialisation of a `const` variable:
%% Cell type:code id: tags:
``` C++17
``` c++
{
int i { 5 };
const int is_strictly_positive = (i > 0 ? 1 : 0);
std::cout << is_strictly_positive << std::endl;
}
```
%% Cell type:markdown id: tags:
With if/else this doesn't work: both ways to do so that might come to mind are flawed:
%% Cell type:code id: tags:
``` C++17
``` c++
{
int i { 5 };
const int is_strictly_positive;
if (i > 0)
is_strictly_positive = 1; // COMPILATION ERROR: can't assign to const value!
// ...
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
int i { 5 };
if (i > 0)
const int is_strictly_positive = 1;
else
const int is_strictly_positive = 0;
std::cout << is_strictly_positive << std::endl; // COMPILATION ERROR: is_strictly_positive not
// in the current scope.
}
```
%% Cell type:markdown id: tags:
### `switch` statement
Very briefly: there is also a `switch` statement that can be used when:
* The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those.
* The relationship considered is an equality.
I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` present in other languages...)
%% Cell type:markdown id: tags:
## Logical operators
A condition might be an amalgation of several conditions. The way to glue it is to use logical operators.
The operators are:
* `&&` for the **and** operator.
* `||` for the **or** operator.
* `!` for the **not** operator.
Actually there are so-called __alternative representations__ using the english name directly for each of them but their usage is not widely spread and not advised from a stylistic standpoint (many StackOverflow threads debate on this topic...).
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a { 2 };
int b { 3 };
if (a == 2 || b == 5)
std::cout << "Ok: first condition is true." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
if (a == 2 or b == 5)
std::cout << "Same as above illustrating the alternative representation." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
if (a == 2 && b == 5)
std::cout << "Not printed: one condition is false." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
if (!(a < 0))
std::cout << "Ok: a < 0 is false so the `not` operator returns true." << std::endl;
```
%% Cell type:markdown id: tags:
You may combine several of them in a more complex condition. `and` takes precedence over `or`, but anyway it's usually better to disambiguate using parenthesis:
%% Cell type:code id: tags:
``` C++17
``` c++
if (a == 5 || a == 2 && b == 3)
std::cout << "(a == 5) and (a == 2 && b == 3) are evaluated separately and the latter is true so this text is printed" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
if ((a == 5) || (a == 2 && b == 3))
std::cout << "Same but easier to grasp." << std::endl;
```
%% Cell type:markdown id: tags:
Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first.
Please notice this means these operators are **not commutative**!
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a { 2 };
int b { 3 };
if (a < 0 && b++ == 3)
;
std::cout << "b was not incremented!: " << b << std::endl;
```
%% Cell type:markdown id: tags:
The story is different if the ordering of the conditions to test is inverted:
%% Cell type:code id: tags:
``` C++17
``` c++
if (b++ == 3 && a < 0)
;
std::cout << "b was incremented: " << b <<
" (as first operation is true second one is evaluated)." << std::endl;
```
%% Cell type:markdown id: tags:
Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands.
%% Cell type:markdown id: tags:
## Loops
%% Cell type:markdown id: tags:
### `while` loop
%% Cell type:markdown id: tags:
The `while` instruction allows you to execute a block of instructions in a loop
as long as a condition is true. The condition is checked **before** each
iteration.
The rules about the instructions belonging to the loop are exactly the same as the ones described for `if` statements.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { };
while (a++ < 5)
std::cout << a << std::endl;
}
```
%% Cell type:markdown id: tags:
If the condition is never true in the first place, we never go inside the loop:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { };
while (a-- < 0)
std::cout << a << std::endl;
std::cout << "Done!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### `do`...`while` loop
%% Cell type:markdown id: tags:
The `do` instruction allows you to execute a block of instructions in a loop as long as
that a condition is true. The condition is verified **after** each
iteration.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a { };
do
{
std::cout << a << std::endl;
} while (a++ < 0); // there is a semicolon here.
}
```
%% Cell type:markdown id: tags:
### `for` loop
%% Cell type:markdown id: tags:
#### Historical `for` loop
%% Cell type:markdown id: tags:
The historical `for` instruction allows you to execute a block of instructions as long as a
condition is true; the difference with the `while` loop is that there are fields explicitly detailing:
* The initial situation.
* The condition to check at the end of each loop.
* What should be changed in next loop if one is called for.
Syntax is:
for (_initial situation_ ; _end loop condition_ ; *evolution for next loop*)
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
for (int i = 0; i < 5; ++i)
std::cout << i << std::endl;
}
```
%% Cell type:markdown id: tags:
Any of these fields might be left empty:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i { }; // i might also be declared and initialized outside the block
for ( ; i < 5; ) // completely equivalent to `while(i < 5)`
{
++i; // i might also be modified within the block
std::cout << i << std::endl;
}
std::cout << "`for` loop stopped for i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
#### New `for` loop
%% Cell type:markdown id: tags:
The `for`syntax we just saw is still very useful, but C++ 11 introduced a new syntax when for instance you want to iterate through all the items in a container, clearly taking inspiration from syntax present in languages such as Python:
for (_type_ _element_ : *container*)
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <vector> // we'll present this one more in detail later
{
std::vector<int> v { 2, 3, 5, 7 };
for (int item : v)
std::cout << item << std::endl;
}
```
%% Cell type:markdown id: tags:
It might not seem much, but just for the record doing the same before C++ 11 was not for the faint of heart...:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <vector> // we'll present this one more in detail later
{
std::vector<int> v { 2, 3, 5, 7 }; // and we're cheating here: C++ 03 syntax was here much worse as well...
for (std::vector<int>::const_iterator it = v.cbegin(),
end = v.cend();
it != end;
++it)
{
std::cout << *it << std::endl;
}
}
```
%% Cell type:markdown id: tags:
### continue, break and infinite loop
%% Cell type:markdown id: tags:
A danger with a loop is to make it infinite: you have to make sure an exit way is foreseen:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
// WARNING: if you run this you will have to restart your kernel! (in Kernel menu)
{
int i { 2 };
while (i > 0) // condition that is true for quite a long time...
std::cout << ++i << " ";
}
```
%% Cell type:markdown id: tags:
The best is to write a palatable condition to end it, but when some loops become increasingly complex you may have to resort to `break`. Be aware it is slightly frowned upon by some programmers, for the very same reasons [goto](https://en.wikipedia.org/wiki/Goto) instructions are avoided.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i { 2 };
while (i > 0)
{
std::cout << i++ << " ";
if (i > 20)
break;
}
}
```
%% Cell type:markdown id: tags:
In this trivial case writing the condition more properly would be of course much better:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int i { 2 };
while (i > 0 && i <= 20)
std::cout << i++ << " ";
}
```
%% Cell type:markdown id: tags:
but honestly in more complex cases `break` can help keep the code more readable.
`continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
for (int i = 2; i < 20; ++i)
{
if (i == 2)
{
std::cout << i << " is even and prime (hello 2!)." << std::endl;
continue;
}
if (i % 2 == 0)
{
std::cout << i << " is even." << std::endl;
continue; // goes directly at the condition checking step in the loop,
// skipping the remaining code below.
}
std::cout << i << " is odd";
bool is_prime = true;
for (int j = 2; j < i / 2; ++j)
{
if (i % j == 0) // % returns the remainder of the division
{
is_prime = false;
break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)'
}
}
std::cout << (is_prime ? " and prime." : ".") << std::endl; // the ternary operator
}
}
```
%% Cell type:markdown id: tags:
Of course, in a trivial example like this one we could have written it much more cleanly without any `continue`, but in more complex cases it is really handful to use it: not using it could lead to code much more complicated to understand, and you really should always strive for code that is the most expressive for a reader.
%% Cell type:markdown id: tags:
### So which loop should I use?
%% Cell type:markdown id: tags:
Whichever you want in fact!
They are mostly interchangeable:
* `while` and (historical) `for` are completely interchangeable, as:
- A `while` loop is exactly like a `for ` loop with only the middle term.
- You can transform a `for` loop into a `while` one: putting the first term before the loop and the third one inside the loop to do so.
* `do..while` behaves slightly differently, but you can always mimic the behaviour with another type of loop.
Lots of programming language define these guys (at least `for` and `while`) so it's useful to know about them, but you can choose one and stick with it as well.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
......@@ -79,16 +79,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Predefined types](./3-Types.ipynb)
%% Cell type:markdown id: tags:
## Boolean
Variables with type `bool` may be set to `true` or `false`.
It should be noted that this type did not originally exist, and that C++ instructions with conditions do not necessarily expect boolean values, but rather integers.
There is a form of equivalence between booleans and integers: any null integer is equivalent to `false`, and any other value is equivalent to `true`.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
bool undefined; // UNDEFINED !!
if (undefined)
std::cout << "This text might appear or not - it's truly undefined and may vary from "
"one run/compiler/architecture/etc... to another!" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
bool defined { true };
if (defined)
std::cout << "Defined!" << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
int n = -5;
if (n)
std::cout << "Boolean value of " << n << " is true." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
int n = 0;
if (!n) // ! is the not operator: the condition is true if n is false.
std::cout << "Boolean value of " << n << " is false." << std::endl;
```
%% Cell type:markdown id: tags:
## Enumerations
### Historical enumerations
The historical enumerations `enum` of C++ allow to define constants that are treated as integers, and that can be initialized from integers. By default the first value is 0 and the `enum` is incremented for each value, but it is possible to bypass these default values and provide the desired numerical value yourself.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
enum color { red, green, blue } ;
std::cout << red << " " << green << " " << blue << " (expected: 0, 1, 2)" << std::endl;
enum shape { circle=10, square, triangle=20 };
std::cout << circle << " " << square << " " << triangle << " (expected: 10, 11, 20)"<< std::endl; // 10 11 20
}
```
%% Cell type:markdown id: tags:
These `enum` are placeholders for integers and might be used as such:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
enum color { red, green, blue } ;
int a { 5 };
color c = green;
int b = a + c;
std::cout << "b = " << b << " (expected: 6)" << std::endl;
enum shape { circle=10, square, triangle=20 };
shape s = triangle;
int d = s + c;
std::cout << "d = " << d << " (expected: 21... but we've just added a shape to a color without ado!)" << std::endl;
}
```
%% Cell type:markdown id: tags:
A (huge) shortcoming of historical `enum ` is that the same word can't be used in two different `enum`:
%% Cell type:code id: tags:
``` C++17
``` c++
{
enum is_positive { yes, no };
enum is_colored { yes, no }; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
### New enumerations
To overcome the two limitations we have just mentioned, C++11 makes it possible to declare new `enum class` enumerations, each constituting a separate type, not implicitly convertible into an integer. This type protects against previous errors at the cost of a little more writing work.
%% Cell type:code id: tags:
``` C++17
``` c++
enum class is_positive { yes, no };
enum class is_colored { yes, no }; // OK
```
%% Cell type:code id: tags:
``` C++17
``` c++
yes; // COMPILATION ERROR: `enum class ` value must be prefixed! (see below)
```
%% Cell type:code id: tags:
``` C++17
``` c++
is_positive p = is_positive::yes; // OK
```
%% Cell type:code id: tags:
``` C++17
``` c++
int a = is_positive::no; // COMPILATION ERROR: not implicitly convertible into an integer
```
%% Cell type:code id: tags:
``` C++17
``` c++
is_positive::yes + is_colored::no; // COMPILATION ERROR: addition of two unrelated types
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
enum class color { red, green, blue } ;
color c = color::green;
bool is_more_than_red = (c > color::red); // Both belong to the same type and therefore might be compared
}
```
%% Cell type:markdown id: tags:
These enum types are really handy to make code more expressive, especially in function calls:
```c++
f(print::yes, perform_checks::no);
```
is much more expressive (and less error-prone) than:
```c++
f(true, false);
```
for which you will probably need to go check the prototype to figure out what each argument stands for.
As we shall see [shortly](#Explicit-conversions-by-static_cast), you may perform arithmetic with the underlying integer through _explicit cast_ of the enum into an integer.
%% Cell type:markdown id: tags:
## Numerical types
#### List of numerical types
The FORTRAN correspondences below are given as examples. The
size of the C++ digital types can vary depending on the processor used. The
standard C++ only imposes `short <= int <= long` and `float <= double <= long double`. This makes these predefined types unportable. Like many things
in C, and therefore in C++, performance is given priority over any other consideration.
The default integer and real types, `int` and `double`, are assumed
to match the size of the processor registers and be the fastest (for more details see [the article on cppreference](http://en.cppreference.com/w/cpp/language/types))
| C++ | Fortran | Observations | 0 notation |
|:------------- |:---------:|:-------------------:|:----------:|
| `short` | INTEGER*2 | At least on 16 bits | None |
| `int` | INTEGER*4 | At least on 16 bits | 0 |
| `long` | INTEGER*8 | At least on 32 bits | 0l |
| `long long` | INTEGER*16| At least on 64 bits | 0ll |
| `float` | REAL*4 | - | 0.f |
| `double` | REAL*8 | - | 0. |
| `long double` | REAL*16 | - | 0.l |
All integer types (`short`, `int` and `long`) also have an unsigned variant, for example
`unsigned int`, which only takes positive values.
It should also be noted that the type `char` is the equivalent of one byte,
and depending on the context will be interpreted as a number or as a
character.
If you need an integer type of a defined size, regardless of the type of processor or platform used, you should use those already defined in `<cstdint>` for C++11 (for more details click [here](http://en.cppreference.com/w/cpp/types/integer)).
The _0 notation column_ is the way to notice explicitly the type in an expression; of course any value might be used instead of 0. A `u` might be used to signal the unsigned status for integer types; for instance `3ul` means 3 as an _unsigned long_. `auto` notation below will illustrate a case in which such a notation is useful.
The STL features rather heavily a type named `std::size_t`, which by design is able to store the maximum size of a theoretically possible object of any type (including array). On most (all?) systems `std::size_t` is an alias to an `unsigned long`. More may be found about this type on [CppReference](https://en.cppreference.com/w/cpp/types/size_t). The equivalent counterpart for *signed* integers is the [`std::ptrdiff_t`](https://en.cppreference.com/w/cpp/types/ptrdiff_t), which is the signed integer type of the result of subtracting two pointers.
#### Numeric limits
Always keep in mind the types of the computer don't match the abstract concept you may use in mathematics... The types stored especially don't go from minus infinity to infinity:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <limits> // for std::numeric_limits
{
std::cout << "int [min, max] = [" << std::numeric_limits<int>::lowest() << ", "
<< std::numeric_limits<int>::max() << "]" << std::endl;
std::cout << "unsigned int [min, max] = [" << std::numeric_limits<unsigned int>::lowest() << ", "
<< std::numeric_limits<unsigned int>::max() << "]" << std::endl;
std::cout << "short [min, max] = [" << std::numeric_limits<short>::lowest() << ", "
<< std::numeric_limits<short>::max() << "]" << std::endl;
std::cout << "long [min, max] = [" << std::numeric_limits<long>::lowest() << ", "
<< std::numeric_limits<long>::max() << "]" << std::endl;
std::cout << "float [min, max] = [" << std::numeric_limits<float>::lowest() << ", "
<< std::numeric_limits<float>::max() << "]" << std::endl;
std::cout << "double [min, max] = [" << std::numeric_limits<double>::lowest() << ", "
<< std::numeric_limits<double>::max() << "]" << std::endl;
std::cout << "long double [min, max] = [" << std::numeric_limits<long double>::lowest() << ", "
<< std::numeric_limits<long double>::max() << "]" << std::endl;
}
```
%% Cell type:markdown id: tags:
(see the [CppReference dedicated page](https://en.cppreference.com/w/cpp/types/numeric_limits) for more details about `std::numeric_limits`).
##### Integral types
If an initial value is not in the range, the compiler will yell:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
short s = -33010; // triggers a warning: outside the range
std::cout << s << std::endl;
}
```
%% Cell type:markdown id: tags:
However, if you go beyond the numeric limit during a computation you're on your own:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <limits> // for std::numeric_limits
{
unsigned int max = std::numeric_limits<unsigned int>::max();
std::cout << "Max = " << max << std::endl;
std::cout << "Max + 1 = " << max + 1 << " // !" << std::endl;
}
```
%% Cell type:markdown id: tags:
When you reach the end of a type, a modulo is actually applied to make put it back into the range!
Don't worry, for most computations you shouldn't run into this kind of trouble, but if you are dealing with important values it is important to keep in mind this kind of issues.
The most obvious way to avoid this is to choose appropriate types: if your integer might be huge a `long` is more appropriate than an `int`.
Other languages such as Python (but not its numeric modules such as _numpy_ which are using C or C++ under the hood) gets a underlying integer model that is resilient to this kind of issue but there is a performance cost behind it; types such as those used in C++ are tailored to favor optimization on your hardware.
%% Cell type:markdown id: tags:
##### Floating-point types
C++ provides special values to represent infinite or not-a-number values for floating-point types (the facilities below appeared after C++ 11; there were others prior to that inherited directly from C).
%% Cell type:code id: tags:
``` C++17
``` c++
#include <cmath>
#include <iostream>
float max_float {std::numeric_limits<float>::max()};
max_float += 1.e+32; // Add something significant enough to max value
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
max_float -= 1.e+32;
std::cout << "Is " << max_float << " infinite ? " << std::isinf(max_float) << std::endl;
double nan = 0. / 0.;
std::cout << "Is " << nan << " infinite ? " << std::isinf(nan) << std::endl;
std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl;
nan = nan + 1.e5;
std::cout << "Is " << nan << " not-a-number ? " << std::isnan(nan) << std::endl;
std::cout << "The unnatural property of nan is that the expression 'nan == nan' is " << std::boolalpha << (nan == nan) << "!" << std::endl;
```
%% Cell type:markdown id: tags:
There are subtleties about NaN (see [Cppreference](https://en.cppreference.com/w/cpp/numeric/math/nan)) but in most cases you don't need to bother much with either `inf` or `nan`, except if you have reasons to think your computation may produce either of them. In that case, you may want to check your value is correct with `std::isfinite` ([Cppreference](https://en.cppreference.com/w/cpp/numeric/math/isfinite)).
%% Cell type:markdown id: tags:
#### Conversions between digital types
[Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) I indicated there were small differences between the three initialization methods, that could be ignored most of the time.
[Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) we indicated there were small differences between the three initialization methods, that could be ignored most of the time.
The difference is related to implicit conversion: both historical initialization methods are ok with implicit conversion __with accuracy loss__:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <iomanip>
{
float f = 1.1234567890123;
double d = 2.1234567890123;
float f_d(d);
float f_dd = d;
float f = 1.123456789;
double d = 2.123456789;
float f_d(d); // Could also have been written: float f_d = d;
std::cout << "A double may print around 15 significant digits, d = " << std::setprecision(16) << d << std::endl;
std::cout << "A float may print around 7 significant digits, f_d = " << std::setprecision(16) << f_d << " so we see here the value was altered from the initial double value." << std::endl;
std::cout << "Even without conversion involved there is an accuracy loss: f = " << std::setprecision(16) << f << std::endl;
std::cout << "Even without conversion involved there is an accuracy loss if we require too high a precision: f = " << std::setprecision(16) << f << std::endl;
}
```
%% Cell type:markdown id: tags:
whereas C++ 11 introduced initialization with braces isn't:
whereas C++ 11 introduced initialization with braces isn't, even if the initial number could fit in the target conversion type:
%% Cell type:code id: tags:
``` C++17
``` c++
{
double d = 2.12345678901234567890;
double d = 2.12345;
float f_d{d}; // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
This is really related to **accuracy loss**: initialization with braces is ok if there are none:
This is really related to **accuracy loss**: initialization with braces is ok only if there is no such loss:
%% Cell type:code id: tags:
``` C++17
``` c++
{
float f = 1.12345678901234567890;
float f = 1.123456;
double d_f { f }; // OK
}
```
%% Cell type:markdown id: tags:
Accuracy losses are detected during conversion:
* from a floating point type (`long double`, `double` and `float`) into an integer type.
* from a `long double` into a `double` or a `float`, unless the source is constant and its value fits into the type of the destination.
* from a `double` into a `float`, unless the source is constant and its value fits in the type of the destination.
* from an integer type to an enumerated or floating point type, unless the source is constant and its value fits into the type of the destination.
* from an integer type to an enumerated type or another integer type, unless the source is constant and its value fits into the type of the destination.
%% Cell type:markdown id: tags:
### Explicit conversions inherited from C
In the case of an explicit conversion, the programmer explicitly says which conversion to use.
C++ inherits the forcing mechanism of the C type:
%% Cell type:code id: tags:
``` C++17
``` c++
{
unsigned short i = 42000 ;
short j = short(i) ;
unsigned short k = (unsigned short)(j) ;
}
```
%% Cell type:markdown id: tags:
It is **not recommended** to use this type of conversion: even if it is clearly faster to type, it can perform a different C++ conversion under the hood and does not stand out clearly when reading a code; it is preferable to use the other conversion modes mentioned below.
%% Cell type:markdown id: tags:
### Explicit conversions by static_cast
C++ has also redefined a family of type forcing,
more verbose but more precise. The most common type of explicit conversion is the `static_cast`:
%% Cell type:code id: tags:
``` C++17
``` c++
{
unsigned short i = 42000;
short j = static_cast<short>(i);
unsigned short k = static_cast<unsigned short>(j);
}
```
%% Cell type:markdown id: tags:
Another advantage of this more verbosy syntax is that you may find it more easily in your code with your editor search functionality.
%% Cell type:markdown id: tags:
### Other explicit conversions
There are 3 other types of C++ conversions:
* `const_cast`, to add or remove constness to a reference or a pointer (obviously to be used with great caution!)
* `dynamic_cast`, which will be introduced when we'll deal with [polymorphism](../2-ObjectProgramming/7-polymorphism.ipynb#dynamic_cast).
* `reinterpret_cast`, which is a very brutal cast which changes the type into any other type, regardless of the compatibility of the two types considered. It is a dangerous one that should be considered only in very last resort (usually when interacting with a C library).
%% Cell type:markdown id: tags:
### Be wary of mathematical operators and types
It was already covered in the Hands-On but please bear in mind that this operation: `2/3` is an integer division, so the result will be an `int`, and might not be what you want.
%% Cell type:code id: tags:
``` C++17
``` c++
{
double a = 2 / 3;
std::cout << a << std::endl; // a == 0
}
```
%% Cell type:markdown id: tags:
If you really want a `float` division, you have to cast at least one of the value:
If you really want a floating-point division, you have to cast at least one of the value:
%% Cell type:code id: tags:
``` C++17
``` c++
{
double a = static_cast<float>(2) / 3;
double a = static_cast<double>(2) / 3;
std::cout << a << std::endl;
// this is also valid
double b = 2. / 3;
std::cout << b << std::endl;
}
```
%% Cell type:markdown id: tags:
Please note that the best would be to explicitly cast both values, to avoid useless implicit conversion.
Please note that the best would be to **explicitly cast both values**, to avoid useless implicit conversion.
%% Cell type:code id: tags:
``` c++
{
double a = 2. / 3.;
std::cout << a << std::endl;
}
```
%% Cell type:markdown id: tags:
## Underlying types of `enum class`
In fact, under the hood `enum class` are storing `int` values by default:
%% Cell type:code id: tags:
``` c++
#include <iostream>
enum class arbitrary_enum_class { yes, no }; // with scoped enum I can choose any value without fearing naming conflict!
// Don't bother if you're lost with `std::is_same_v<int, std::underlying_type_t<int_enum>>` - we'll cover that later in part 5.
// You just have to know here it checks whether underlying type behind enum is an `int` or not.
std::cout << "Check underlying type behind `arbitrary_enum_class` is `int`: " << std::boolalpha << std::is_same_v<int, std::underlying_type_t<arbitrary_enum_class>> << '\n';
```
%% Cell type:markdown id: tags:
However it is possible to choose another type by specifying it explicitly after a colon:
%% Cell type:code id: tags:
``` c++
enum class altitude : char // telling here explicitly to use `char` as underlying type
{
high = 'h',
low = 'l'
};
std::cout << "Check underlying type behind `altitude` is not `int`: " << std::boolalpha << std::is_same_v<int, std::underlying_type_t<altitude>> << '\n';
std::cout << "Check underlying type behind `altitude` is `char`: " << std::boolalpha << std::is_same_v<char, std::underlying_type_t<altitude>> << '\n';
```
%% Cell type:markdown id: tags:
## Characters and strings
### Historical strings
In C, a character string is literally an array of `char` variables, the last character of which is by convention the symbol `\0`.
The `strlen` function returns the length of a string, which is the number of characters between the very first character and the first occurrence of `\0`.
The `strcpy` function copies a character string to a new memory location; care must be taken to ensure that the destination is large enough to avoid any undefined behavior.
The `strncpy` function allows you to copy only the first <b>n</b> first characters, where <b>n</b> is the third parameter of the function. Same remark about the need to foresee a large enough destination.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <cstring> // For strlen, strcpy, strncpy
char hello[] = {'h','e','l','l','o', '\0'};
char copy[6] = {}; // = {'\0','\0','\0','\0','\0','\0' };
strcpy(copy, hello);
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
const char* hi = "hi"; // Not putting the const here triggers a warning.
strncpy(copy, hi, strlen(hi));
copy[strlen(hi)] = '\0'; // Don't forget to terminate the string!
std::cout << "String '" << copy << "' is " << strlen(copy) << " characters long." << std::endl;
```
%% Cell type:markdown id: tags:
There are several other functions related to historical strings; for more information, do not hesitate to consult [this reference page](http://www.cplusplus.com/reference/cstring/).
#### Drawbacks of historical strings
- Keeping track properly and not forgetting `\0` gets old really fast and the outcome is usually not user-friendly...
- Assignment doesn't work as you would expect:
%% Cell type:code id: tags:
``` c++
#include <iostream>
// Define hello, and copy it
char hello[] = {'h','e','l','l','o', '\0'};
const char* copy = hello;
// Modify hello
hello[0] = '!';
// `copy` is modified too!
std::cout << "Value of 'copy' is not the 'hello' you probably wanted: " << copy << std::endl;
```
%% Cell type:markdown id: tags:
### std::string
In modern C++, rather than bothering with character tables
which come from the C language, it's easier to use the type `std::string`, provided
through the standard language library, that provides a much simpler syntax:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <cstring> // For strlen
#include <string> // For std::string
const char* hello_str = "hello";
std::string hello = hello_str;
std::string hi("hi");
std::string copy {};
```
%% Cell type:code id: tags:
``` C++17
copy = hello; // please notice assignment is much more straightforward
``` c++
copy = hello; // please notice assignment is much more straightforward!
hello = "hi";
std::cout << "String '" << copy << "' is " << copy.length() << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
const char* copy_str = copy.data(); // Returns a classic C-string (from C++11 onward)
std::cout << "String '" << copy_str << "' is " << strlen(copy_str) << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
const char* old_copy_str = &copy[0]; // Same before C++11...
std::cout << "String '" << old_copy_str << "' is " << strlen(old_copy_str) << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` c++
std::string dynamic {"dynamic"};
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
``` C++17
std::string dynamic {"dynamic std::string"};
dynamic += " string";
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
dynamic = "std::string is dynamical and flexible";
std::cout << "String '" << dynamic << "' is " << dynamic.length() << " characters long." << std::endl;
```
%% Cell type:markdown id: tags:
If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()` (both are interchangeable):
If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <string>
{
std::string cplusplus_string("C++ string!");
const char* c_string = cplusplus_string.c_str();
const char* c_string_2 = cplusplus_string.data();
}
```
%% Cell type:markdown id: tags:
The `const` here is important: you may access the content but should not modify it; this functionality is provided for read-only access.
Both are interchangeable; `data()` was introduced in C++ 11 but is more generic as other containers also define it - we'll cover that in part 5.
As the principle to "convert" to C string is to return the address of the first character, you may also meet in some legacy codes:
%% Cell type:code id: tags:
``` c++
#include <string>
{
std::string cplusplus_string("C++ string!");
const char* c_string = &cplusplus_string[0];
}
```
%% Cell type:markdown id: tags:
which tells to litteraly take address of the first element.
%% Cell type:markdown id: tags:
FYI, C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some operations (it is presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now).
FYI, C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some read-only operations (it is presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now).
%% Cell type:markdown id: tags:
## Renaming types
Sometimes it may be handy to rename a type, for instance if you want to be able to change easily throughout the code the numeric precision to use. Historical syntax (up to C++ 11 and still valid) was `typedef`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <iomanip> // For std::setprecision
{
typedef double real; // notice the ordering: new typename comes after its value
real radius {1.};
real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
}
```
%% Cell type:markdown id: tags:
In more modern C++ (C++11 and above), another syntax relying on `using` keyword was introduced; it is advised to use it as this syntax is more powerful in some contexts (see later with templates...):
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <iomanip> // For std::setprecision
{
using real = float; // notice the ordering: more in line with was we're accustomed to when
// initialising variables.
real radius {1.};
real area = 3.1415926535897932385 * radius * radius;
std::cout <<"Area = " << std::setprecision(15) << area << std::endl;
}
```
%% Cell type:markdown id: tags:
## `decltype` and `auto`
C++ 11 introduced new keywords that are very handy to deal with types:
* `decltype` which is able to determine **at compile time** the underlying type of a variable.
* `auto` which determines automatically **at compile time** the type of an expression.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
{
auto i = 5; // i is here an int.
auto j = 5u; // j is an unsigned int
decltype(j) k; // decltype(j) is interpreted by the compiler as an unsigned int.
}
```
%% Cell type:markdown id: tags:
On such trivial examples it might not seem much, but in practice it might prove incredibly useful. Consider for instance the following versions of the code for iterating over a vector with an 'historical' `for` loop (the details don't matter: we'll deal with `std::vector` in a [later notebook](../5-UsefulConceptsAndSTL/3-Containers.ipynb)):
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 03 way of iterating over the content of a vector.
for (std::vector<unsigned int>::const_iterator it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
It's very verbose; we could of course use an alias:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
using iterator = std::vector<unsigned int>::const_iterator;
// C++ 03 way of iterating over the content of a vector - with an alias
for (iterator it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
But with `decltype` we may write instead:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 decltype
for (decltype(primes.cbegin()) it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
or even better:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
#include <iostream>
{
std::vector<unsigned int> primes { 2, 3, 5 };
// C++ 11 auto
for (auto it = primes.cbegin();
it != primes.cend();
++it)
{
std::cout << *it << " is prime." << std::endl;
}
}
```
%% Cell type:markdown id: tags:
That is not to say `decltype` is always inferior to `auto`: there are some cases in which decltype is invaluable (especially in metaprogramming, but it's mostly out of the scope of this lecture - we'll skim briefly over it in a later [notebook](../4-Templates/4-Metaprogramming.ipynb)).
C++ 14 introduced a new one (poorly) called `decltype(auto)` which usefulness will be explained below:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <algorithm>
#include <iostream>
int i = 5;
int& j = i;
auto k = j;
if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl;
else
std::cout << "j and k are of different type." << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
if (std::is_same<decltype(i), decltype(k)>())
std::cout << "i and k are of the same type." << std::endl;
else
std::cout << "i and k are of different type." << std::endl;
```
%% Cell type:markdown id: tags:
Despite the `auto k = j`, j and k don't share the same type! The reason for this is that `auto` loses information about pointers, reference or constness in the process...
A way to circumvent this is `auto& k = j`.
`decltype(auto)` was introduced to fill this hole: contrary to `auto` it retains all these information:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <algorithm>
#include <iostream>
{
int i = 5;
int& j = i;
decltype(auto) k = j;
if (std::is_same<decltype(j), decltype(k)>())
std::cout << "j and k are of the same type." << std::endl;
else
std::cout << "j and k are of different type." << std::endl;
}
```
%% Cell type:markdown id: tags:
Fore more details about `auto`'s type deduction, you can check the C++ weekly videos at [this link](https://www.youtube.com/watch?v=tn69TCMdYbQ) and also [this one](https://www.youtube.com/watch?v=E5L66fkNlpE) regarding `decltype(auto)`'s usage.
%% Cell type:markdown id: tags:
### `auto` and string literals
**Beware:** when you declare a string literals with `auto`, the type deduction makes it a `const char*`, not a `std::string`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <algorithm>
#include <string>
#include <iostream>
auto hello_str = "Hello world"; // declares a char*
std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl;
std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl;
```
%% Cell type:markdown id: tags:
C++ 14 introduced a suffix to facilitate declaration of a `std::string` from a string literals... but which requires to add a specific `using namespace` first (we will see that those are in a [much later notebook](../6-InRealEnvironment/5-Namespace.ipynb)).
%% Cell type:code id: tags:
``` C++17
``` c++
#include <string>
using namespace std::string_literals;
std::string hello_string("Hello world"); // the 'classic' way to define a std::string
auto hello_str = "Hello world"s; // declares a std::string - requires first the using namespace directive
std::cout << "Is 'hello_string' a const char*? " << std::boolalpha << std::is_same<decltype(hello_string), const char*>() << std::endl;
std::cout << "Is 'hello_string' a std::string? " << std::boolalpha << std::is_same<decltype(hello_string), std::string>() << std::endl;
std::cout << "Is 'hello_str' a const char*? " << std::boolalpha << std::is_same<decltype(hello_str), const char*>() << std::endl;
std::cout << "Is 'hello_str' a std::string? " << std::boolalpha << std::is_same<decltype(hello_str), std::string>() << std::endl;
```
%% Cell type:markdown id: tags:
Not sure it it is entirely worth it (maybe when you define loads of `std::string` is a same file?) but you may see that syntax in an existing program.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Functions](./4-Functions.ipynb)
%% Cell type:markdown id: tags:
## Function declaration and definition
### Function declaration
The aim of a **function declaration** is just to describe its prototype:
- The return type of the function (or `void` if the function returns nothing).
- The number, type and ordering of the parameters (if any). Naming them specifically is entirely optional (but is useful if you choose to put your [Doxygen](../6-InRealEnvironment/6-Tools.ipynb#Doxygen) documentation along your functions declarations - see below).
Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere.
Few examples of function declarations:
%% Cell type:code id: tags:
``` C++17
``` c++
int ComputeMinimum(int a, int b);
```
%% Cell type:code id: tags:
``` C++17
``` c++
void DoStuff(double); // naming the parameter is optional
```
%% Cell type:code id: tags:
``` C++17
``` c++
int ReturnFive(); // providing a parameter is also optional...
// Don't bother Xeus-cling warning: it IS here a function declaration!
// Don't bother the notebook kernel warning: it IS here a function declaration!
```
%% Cell type:markdown id: tags:
Due to the use of notebooks we will not need to separate clearly function declaration from its definition, but we will revisit this [much later](../6-InRealEnvironment/2-FileStructure.ipynb) when we will see how a real project is written with different files involved.
%% Cell type:markdown id: tags:
### Function definition
%% Cell type:markdown id: tags:
On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand.
In a function definition:
- No semicolon after the prototype
- A block follows the prototype instead; inside this block the implementation is written.
- Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation.
- No semicolon after the closing brace.
For instance:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintDivision(int numerator, int denominator) // no semicolon here!
{
if (denominator == 0)
std::cout << "Failure: division by zero!" << std::endl;
else
{
int division ;
division = numerator / denominator ;
std::cout << numerator << " / " << denominator << " = " << division << std::endl ;
}
}
} // no semicolon here as well!
```
%% Cell type:markdown id: tags:
and when the function is invoked at some point, the implementation above is directly put in motion:
%% Cell type:code id: tags:
``` C++17
``` c++
int num = 3;
int denom = 2;
PrintDivision(num, denom);
```
%% Cell type:markdown id: tags:
#### A terminology note: _parameter_ and _argument_
In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**.
For the purists:
- **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition)
- **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell)
I do not guarantee that I am using the right term everywhere in the code: I'm not a purist and often use one for another (if you want to remember properly a helpful mnemotechnic is that **a**rguments are **a**ctual).
%% Cell type:markdown id: tags:
#### Functions cannot be nested, or declared within blocks
%% Cell type:markdown id: tags:
Functions cannot be nested in C++, contrary to some other langages such as Python:
Functions cannot be nested in C++, contrary to some other languages such as Python:
%% Cell type:code id: tags:
``` C++17
``` c++
void Function1() // a function might have no arguments
{
void Subfunction() // COMPILATION ERROR!
{
}
}
```
%% Cell type:markdown id: tags:
To reintroduce hierarchy, __namespaces__ can be used (they will be introduced [a bit later](../6-InRealEnvironment/5-Namespace.ipynb)); __lambda functions__ introduced later in this notebook are not limited by the same rule.
%% Cell type:markdown id: tags:
## How to pass arguments
### Passing arguments by value
In the simple example above, we passed the arguments by value, which is to say the values passed by the arguments were copied when given to the function:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void IncrementAndPrint(int i)
void IncrementAndPrint(int value)
{
++i;
std::cout << "Inside the function: i = " << i << std::endl;
++value;
std::cout << "Inside the function: value = " << value << std::endl;
}
{
int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
int i { 5 };
IncrementAndPrint(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
The `i` in the block body and in the function definition are not the same: one or the other could have been named differently and the result would have been the same:
#### Naming and scope
%% Cell type:code id: tags:
%% Cell type:markdown id: tags:
``` C++17
#include <iostream>
In the above example, the lonely parameter of the function `IncrementAndPrint` was named `value`, whereas at call site the variable was simply named `i`.
void IncrementAndPrint2(int local_argument)
We could as well have used the same name in both places - there are no ambiguity whatsoever in doing so.
%% Cell type:code id: tags:
``` c++
void IncrementAndPrintUseI(int i)
{
++local_argument;
std::cout << "Inside the function: local_argument = " << local_argument << std::endl;
++i;
std::cout << "Inside the function: i = " << i << std::endl;
}
{
int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
int i { 5 };
IncrementAndPrint2(i);
IncrementAndPrintUseI(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
This might seem idiotic (and sure enough giving a name as simple as a one letter character is misguided in most cases!), but on
the other hand when you're considering a mathematical or physical quantity (`jacobian`, `displacement`, etc... ) you do not want
to force yourself to find another name (naming is already hard enough as it is).
The reason there are no ambiguity at all is that in each scope (remember [first notebook](../1-ProceduralProgramming/1-Variables.ipynb#Scope-and-blocks))
there is at most one `i` defined.
%% Cell type:markdown id: tags:
### Passing arguments by reference
If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void IncrementAndPrintByReference(int& i)
{
++i;
std::cout << "Inside the function: i = " << i << std::endl;
}
{
int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByReference(i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
As in C++ you cannot return several values in the return type, passing by reference is a way to get in output several values (C++ 11 introduced in the standard library a workaround to get several values in return type with a so-called `std::tuple`, but the passing by reference remains the better way to do so in most cases).
%% Cell type:code id: tags:
``` C++17
``` c++
int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)
{
if (arg2 == 0)
return -1; // error code.
quotient = arg1 / arg2;
remainder = arg1 % arg2;
return 0; // code when everything is alright.
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int quotient, remainder;
if (ComputeDivision(5, 3, quotient, remainder) == 0)
std::cout << "5 / 3 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
if (ComputeDivision(5, 0, quotient, remainder) == 0)
std::cout << "5 / 0 = " << quotient << " with a remainder of " << remainder << '.' << std::endl;
else
std::cerr << "Can't divide by 0!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### A bit of wandering: using C-like error codes
The function above gets two outputs: the quotient and the remainder of the euclidean division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider.
Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `ComputeDivision()` as if it was a void function is perfectly fine (and yet completely ill-advised). We will see [later](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb) the `exception` mechanism C++ recommends instead of error codes (and discuss a bit more error codes as well).
Below is an example where things go awry due to the lack of check:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintDivision(int arg1, int arg2)
{
int quotient, remainder;
ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl;
}
PrintDivision(8, 5);
PrintDivision(8, 0); // bug!
PrintDivision(8, 0); // prints garbage values...
```
%% Cell type:markdown id: tags:
The developer made two important mistakes:
* The return value of `ComputeDivision` is not checked, so something is printed on screen.
* This is something completely out of control: quotient and remainder do not get a default value that would help to see if something is askew. The behaviour is undefined: you have no guarantee on the values the program will print (currently I see the same values as in the previous function call, but another compiler/architecture/etc... might yield another wrong value.
%% Cell type:markdown id: tags:
#### [[nodiscard]]
C++ 17 introduced a keyword named `[[nodiscard]]` that mitigates the issue mentioned above; this keyword tells the return value must be checked and not doing so is a **compilation warning**.
It doesn't prevent a developer to use improperly the `ComputeDivision` function, but at least they are warned that they're doing something wrong.
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
#include <iostream>
// Declarations
int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder);
void PrintDivision(int arg1, int arg2);
// Definitions
[[nodiscard]] int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)
{
if (arg2 == 0)
return -1; // error code.
quotient = arg1 / arg2;
remainder = arg1 % arg2;
return 0; // code when everything is alright.
}
void PrintDivision(int arg1, int arg2)
{
int quotient, remainder;
ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.
std::cout << "Euclidean division of " << arg1 << " by " << arg2 << " yields a quotient of "
<< quotient << " and a remainder of " << remainder << std::endl;
}
int main([[maybe_unused]] int argc, char** argv)
{
PrintDivision(8, 0); // bug!
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Passing arguments by pointers
When the argument of a function is a pointer, each function call
results in the creation of a temporary pointer which is given the address provided as argument. Then using the `*` operator, you can access the
original variable, not a copy.
Except in the case of interaction with a C library or some _very_ specific cases, I wouldn't advise using passing arguments by pointers: by reference does the job as neatly and in fact more efficiently (dereferencing a pointer `i` with `*i` syntax is not completely costless performance-wise).
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void IncrementAndPrintByPointer(int* i)
{
*i += 1;
std::cout << "Inside the function: i = " << *i << std::endl;
}
{
int i = 5; // I could have named it differently - it doesn't matter as the scope is different!
IncrementAndPrintByPointer(&i);
std::cout << "Outside the function: i = " << i << std::endl;
}
```
%% Cell type:markdown id: tags:
## Function with return value
The value to return should come after the keyword `return`.
A C++ function may include several return values in its implementation:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
//! \brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative.
int Sign(int a)
{
if (a > 0)
return 1;
if (a == 0)
return 0;
return -1;
}
{
for (int a = -3; a < 4; ++a)
std::cout << "Sign of " << a << " = " << Sign(a) << std::endl;
}
```
%% Cell type:markdown id: tags:
## Alternate function syntax
There is now since C++ 11 another way to declare a function using so called `trailing return types`; it is not widespread but is advised by some developers (see for instance [this blog post](https://blog.petrzemek.net/2017/01/17/pros-and-cons-of-alternative-function-syntax-in-cpp/) which lists pros and cons of both syntaxes).
%% Cell type:code id: tags:
``` C++17
auto Sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment
``` c++
%%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
auto Sum(int a, int b) -> int
{
return a + b;
}
```
%% Cell type:markdown id: tags:
The return type is optional (and was the reason of Xeus-cling failure):
The return type is optional:
%% Cell type:code id: tags:
``` C++17
auto Sum(int a, int b) // compiles just fine in Xeus-cling
``` c++
auto SumWithoutExplicitReturnType(int a, int b)
{
return a + b;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int a = 8;
int b = -3;
std::cout << a << " + " << b << " = " << Sum(a, b) << std::endl;
std::cout << a << " + " << b << " = " << SumWithoutExplicitReturnType(a, b) << std::endl;
```
%% Cell type:markdown id: tags:
## Function overload
### The easy cases: arguments without ambiguity
It is possible to define several different functions with the exact same name, provided the type of the argument differ:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <string>
void F();
void F(int); // Ok
double F(int, double); // Ok
auto F(char) -> int; // Ok (alternate function syntax)
std::string F(double, int, char*); // Ok
```
%% Cell type:markdown id: tags:
### **[WARNING]** Return type does not count!
It is not the entire signature of the function that is taken into account when possible ambiguity is sought by the compiler: the return type is not taken into account. So the following cases will not be valid:
%% Cell type:code id: tags:
``` C++17
``` c++
void G(int);
int G(int); // COMPILATION ERROR
```
%% Cell type:markdown id: tags:
If we think about it, it is rather logical: in C++ we are not required to use the return type of a function (it's not the case in all languages: Go follows a different path on that topic for instance). The issue then is that the compiler has no way to know which `g(int)` is supposed to be called with `g(5)` for instance.
%% Cell type:markdown id: tags:
### **[WARNING]** This is a C++ only feature and will not work in C!
%% Cell type:markdown id: tags:
In C you cannot do the following: if you run a simple program with overload:
```c
#include <stdio.h>
void f()
{
printf("No argument version");
}
void f(int a)
{
printf("Int argument version");
}
int main()
{
return 0;
}
```
you will get error messages such as:
```shell
prog.c:8:6: error: redefinition of 'f'
8 | void f(int a)
| ^
prog.c:3:6: note: previous definition of 'f' with type 'void()'
3 | void f()
```
(you may check this with [Wandbox](https://wandbox.org/) and select C instead of C++ as language).
%% Cell type:markdown id: tags:
If you're interested to understand why, you may read [this Wikipedia page](https://en.wikipedia.org/wiki/Name_mangling) (to put in a nutshell, C and C++ chose to handle very differently how to handle the symbols; C++ will use something called _mangling_ to be able to disambiguate between the different overloads).
%% Cell type:markdown id: tags:
### Good practice: do not make signature vary only by a reference or a pointer
%% Cell type:markdown id: tags:
On the other hand, compiler is completely able to accept signatures that differs only by a reference or a pointer on one of the argument:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void H(double a)
{
std::cout << "h(double) is called with a = " << a << '.' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void H(double* a) // Ok
{
std::cout << "h(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void H(double& a) // Ok... but not advised! (see below)
{
std::cout << "h(double&) is called with a = " << a << "; a is doubled by the function." << std::endl;
a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
H(5); // Ok
double x = 1.;
H(&x); // Ok
}
```
%% Cell type:markdown id: tags:
However, there is a possible ambiguity between the pass-by-copy and pass-by-reference:
%% Cell type:code id: tags:
``` C++17
``` c++
{
double x = 1.;
H(x); // COMPILATION ERROR: should it call h(double) or h(double& )?
}
```
%% Cell type:markdown id: tags:
You can lift the ambiguity for the pass-by-value:
%% Cell type:code id: tags:
``` C++17
``` c++
{
double x = 1.;
H(static_cast<double>(x)); // Ok
}
```
%% Cell type:markdown id: tags:
But not to my knowledge for the pass-by-reference... So you should really avoid doing so: if you really need both functions, name them differently to avoid the ambiguity.
I would even avoid the pointer case: granted, there is no ambiguity for a computer standpoint, but if you get a developer who is not 100% clear about the pointer syntax he might end-up calling the wrong function:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void H2(double a)
{
std::cout << "h2(double) is called with a = " << a << '.' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void H2(double* a)
{
std::cout << "h2(double*) is called with a = " << *a << "; a is doubled by the function." << std::endl;
*a *= 2.;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
double x = 5.;
double* ptr = &x;
H2(x); // call h2(double)
H2(ptr); // call h2(double*)
H2(*ptr); // call h2(double)
H2(&x); // call h2(double*)
}
```
%% Cell type:markdown id: tags:
### Best viable function
In fact, overloading may work even if the match is not perfect: the **best viable function** is chosen if possible... and some ambiguity may appear if none matches!
The complete rules are very extensive and may be found [here](https://en.cppreference.com/w/cpp/language/overload_resolution); as a rule of thumb you should really strive to write overloaded functions with no easy ambiguity... or not using it at all: sometimes naming the function differently avoids loads of issues!
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int Min(int a, int b)
{
std::cout << "int version called!" << std::endl;
return a < b ? a : b;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
double Min(double a, double b)
{
std::cout << "double version called!" << std::endl;
return a < b ? a : b;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
int i1 { 5 }, i2 { -7 };
double d1 { 3.14}, d2 { -1.e24};
float f1 { 3.14f }, f2 { -4.2f};
short s1 { 5 }, s2 { 7 };
Min(5, 7); // no ambiguity
Min(i1, i2); // no ambiguity
Min(f1, f2); // conversion to closest one
Min(f1, d2); // conversion to closest one
Min(s1, s2); // conversion to closest one
}
```
%% Cell type:markdown id: tags:
However, with some other types it doesn't work as well if implicit conversion is dangerous and may loose data:
%% Cell type:code id: tags:
``` C++17
``` c++
{
unsigned int i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
long i1 { 5 }, i2 { 7 };
Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!
}
```
%% Cell type:markdown id: tags:
Likewise, if best candidate is not the same for each argument:
%% Cell type:code id: tags:
``` C++17
``` c++
{
float f1 { 5.f };
int i1 { 5 };
Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...
}
```
%% Cell type:markdown id: tags:
### Advice: use overload only when there is no ambiguity whatsoever
That is when:
- The number of arguments is different between overloads.
- Or their types do not convert implicitly from one to another. For instance the following overloads are completely safe to use and the interface remains obvious for the end-user:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <string>
#include <iostream>
std::string GenerateString()
{
std::cout << "No argument!";
return "";
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::string GenerateString(char one_character)
{
std::cout << "One character: ";
return std::string(1, one_character);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::string GenerateString(char value1, char value2)
{
std::cout << "Two characters: ";
std::string ret(1, value1);
ret += value2;
return ret;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::string GenerateString(const std::string& string)
{
std::cout << "Std::string: ";
return string;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
std::string GenerateString(const char* string)
{
std::cout << "Char*: ";
return std::string(string);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
std::cout << GenerateString() << std::endl;
std::cout << GenerateString('a') << std::endl;
std::cout << GenerateString('a', 'b') << std::endl;
std::cout << GenerateString("Hello world!") << std::endl;
std::string text("Hello!");
std::cout << GenerateString(text) << std::endl;
}
```
%% Cell type:markdown id: tags:
## Optional parameters
It is possible to provide optional parameters in the **declaration** of a function:
%% Cell type:code id: tags:
``` C++17
``` c++
%%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
// Declaration.
void FunctionWithOptional(double x, double y = 0., double z = 0.);
```
%% Cell type:code id: tags:
``` C++17
``` c++
%%cppmagics cppyy/cppdef
// < Notebook-related line - without it the perfectly valid C++ syntax below wouldn't be accepted. Don't bother!
#include <iostream>
// Definition
void FunctionWithOptional(double x, double y, double z) // notice the absence of default value!
{
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
// WARNING: Xeus-cling issue!
``` c++
{
FunctionWithOptional(3., 5., 6.); // ok
FunctionWithOptional(3.); // should be ok, but Xeus-cling issue.
FunctionWithOptional(3., 5., 6.);
FunctionWithOptional(3.);
}
```
%% Cell type:markdown id: tags:
The reason not to repeat them is rather obvious: if both were accepted you may modify one of them and forget to modify the others, which would be a bad design...
There is a way to put it in the same place, that I do not recommend it (and your compiler should warn you most of the time): if you do not declare the function beforehand, default arguments may be specified at definition:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
// Definition which double acts as declaration
void FunctionWithOptional2(double x, double y = 0., double z = 0.)
{
std::cout << '(' << x << ", " << y << ", " << z << ')' << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
FunctionWithOptional2(3., 5., 6.); // ok
FunctionWithOptional2(3.); // ok
}
```
%% Cell type:markdown id: tags:
In C and C++, arguments are only **positional**: you do not have a way to explicitly set an argument with a name for instance.
Therefore:
* Optional arguments must be put together at the end of the function.
* You must think carefully if there are several of them and put the less likely to be set manually by the function user at then end. In our example above, if you want do call the function with a `x` and a `z` you must mandatorily also provide explicitly `y`.
%% Cell type:markdown id: tags:
## Lambda functions
C++ 11 introduced a shorthand to define functions called __lambda functions__.
An example is the best way to introduce them:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
// Locally defined function.
auto Square = [](double x) -> double
{
return x * x;
};
std::cout << Square(5.) << std::endl;
}
```
%% Cell type:markdown id: tags:
Several notes:
* Use `auto` as its return type; said type is not reproducible (see the _square_ and _cube_ example below).
* The symbol `->` that specifies the type of the returned value is optional.
* Parameters come after the `[]` in parenthesis with the same syntax as ordinary functions.
* This is not the same as the [alternate syntax](../1-ProceduralProgramming/4-Functions.ipynb#Alternate-function-syntax) explained earlier, even if they look similar: a lambda may be defined locally (here within a block) whereas a standard function (with usual or alternate syntax) can't.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
// Locally defined function.
auto Square = [](double x)
{
return x * x;
};
auto Cube = [](double x)
{
return x * x * x;
};
std::cout << "Are the lambda prototypes the same type? "
<< (std::is_same<decltype(Square), decltype(Cube)>() ? "true" : "false") << std::endl;
}
```
%% Cell type:markdown id: tags:
Inside the `[]` you might specify values that are transmitted to the body of the function; by default nothing is transmitted:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a = 5;
auto APlusB = [](int b)
{
return a + b;
};
std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a = 5;
auto APlusB = [a](int b) // Notice the `[a]` here!
{
return a + b;
};
std::cout << APlusB(3) << std::endl;
}
```
%% Cell type:markdown id: tags:
The values captured in the lambda might be transmitted by reference:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int a = 5;
auto AddToA = [&a](int b) // Notice the `[&a]` here!
{
a += b;
};
AddToA(3);
std::cout << a << std::endl;
}
```
%% Cell type:markdown id: tags:
It is possible to capture everything (in the scope where the lambda is defined) by reference by using `[&]` but it is really ill-advised; don't do this!
Lambda functions really shines when you want to use them in a very special context; see below an example using the [`std::sort`](https://en.cppreference.com/w/cpp/algorithm/sort) function provided by the standard library (don't worry about `std::sort` - we will address it later in the notebook dedicated to [algorithms](../5-UsefulConceptsAndSTL/7-Algorithms.ipynb). If you want to know more you may also consult [cppreference](https://en.cppreference.com/w/cpp/algorithm/sort)).
Let's imagine that for some reasons we want to sort integers in a weird fashion: first the odd numbers properly ordered and then the even numbers. We can give this admittedly pointless choice through a lambda:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
#include <iostream>
#include <algorithm> // for sort
{
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::cout << "Initial list = ";
for (int value : list)
std::cout << value << ' ';
// My very specific sort operation:
// Returns true if lhs is odd and rhs isn't or if lhs < rhs.
auto odd_first = [](auto lhs, auto rhs)
{
const bool is_lhs_odd = !(lhs % 2 == 0);
const bool is_rhs_odd = !(rhs % 2 == 0);
if (is_lhs_odd != is_rhs_odd)
return is_lhs_odd;
return lhs < rhs;
};
std::sort(list.begin(), list.end(), odd_first);
std::cout << std::endl << "Sorted list = ";
for (int value : list)
std::cout << value << ' ';
}
```
%% Cell type:markdown id: tags:
Please notice the use of an intermediate local variable for the lambda is not mandatory; the lambda may be provided on the fly:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <algorithm>
#include <iostream>
{
std::vector<int> list { 3, 5, 2, -4, 8, -17, 99, 15, 125447, 0, -1246 };
std::vector<int> even_only;
// Don't worry about the syntax of `copy_if` or `back_inserter` here; we will see that later!
std::copy_if(list.cbegin(),
list.cend(),
std::back_inserter(even_only),
[](int value)
{
return value % 2 == 0;
});
for (int value : even_only)
std::cout << value << ' ';
}
```
%% Cell type:markdown id: tags:
## Passing a function as a an argument
In some cases, you might want to pass a function as an argument (and honestly most of the time you should refrain to do so: it may underline your design is not top notch).
The syntax to do so is a bit ugly and stems directly from C; it relies upon using a pointer to a function.
The syntax looks like:
```c++
unsigned int (*f) (int, double)
```
where:
* `unsigned int` is the return type.
* `int, double` are the type of the parameters of the function given as argument.
* `f` is the name of the argument.
It will be clearer in an example:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintFunctionCall(int (*f) (int, int), int m, int n)
{
std::cout << "f(" << m << ", " << n << ") = " << f(m, n) << std::endl;
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
int Multiply(int a, int b)
{
return a * b;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
int Add(int a, int b)
{
return a + b;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
PrintFunctionCall(Multiply, 5, 6);
PrintFunctionCall(Add, 5, 6);
```
%% Cell type:markdown id: tags:
There are other ways to do this task:
* Using a template parameter. Templates will be reached [later in this tutorial](../4-Templates/0-main.ipynb), but for me it's usually the way to go.
* Using [functors](../3-Operators/5-Functors.ipynb)
* Using `std::function`, introduced in C++ 11. However <a href="https://vittorioromeo.info/index/blog/passing_functions_to_functions.html">this blog</a> explains why it's not a good idea; on top of the arguments given there it doesn't seem to respect the prototype closely (a function with double instead of int is for instance accepted).
%% Cell type:markdown id: tags:
## A very special function: __main__
Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where:
* __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2.
* __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given.
Please notice the internal mechanics of C/C++ compiler returns these values; if a user type `isPrime qwerty 20`, the main functions will return argc = 3. It is up to the writer of the main to ensure the arguments are correct.
If some of these values should be interpreted as numbers, it is also up to the developer to foresee the conversion from the C-string to a numerical value.
In the very specific of our Jupyter notebook, a unique main might be defined or not in the file: _cling_ performs some magic to generate one under the hood.
The __main__ function may also be defined as __int main()__ without arguments if the program doesn't actually need any.
Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers.
The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes.
The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes. These two macros are defined inside `cstdlib` header.
We will deal with main functions later when we will work in a true C++ environment.
%% Cell type:markdown id: tags:
## Unused arguments
It might happen that a function provides parameters which are not actually used in the implementation; if you've set up the appropriate warning flags for your compiler, it should warn you about these unused parameters:
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int argc, char** argv)
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
To counteract this you have three strategies:
%% Cell type:markdown id: tags:
### Not naming the parameters
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int , char** )
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Casting the parameters to void
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main(int argc, char** argv)
{
static_cast<void>(argc);
static_cast<void>(argv);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
### Using [[maybe_unused]] (since C++ 17)
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
We strongly advised you to use the latest, if your code is using at least C++ 17:
- Not naming the parameters might seem innocuous here, but in more meaningful codes you lose expressivity: the fact that the argument isn't used doesn't mean you don't want the intel about what it is about (we'll see later in object programming `override` [we'll see later](../2-ObjectProgramming/7-polymorphism.ipynb#override-keyword)). Naming bears information, and dropping it is therefore loss of information.
- Casting to `void` is not straightforward for developers who don't know the idiom. Even so, the fact that it's not directly indicated at the declaration site makes it less expressive.
`[[maybe_unused]]` may in fact be used as well for local variables:
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
int a;
return EXIT_SUCCESS;
}
```
%% Cell type:code id: tags:
``` c++
%%cppmagics --print_command clang
#include <cstdlib>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
[[maybe_unused]] int a; // fixes the warning
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## `inline` functions
You may also in a function declaration and definition function prepend the prototype by an `inline`. This indicates the compiler this function might be **inlined**: this means the content of the function may be copied directly, thus avoiding a function call and potentially making your code a tiny bit faster. So for instance if you have a function:
%% Cell type:code id: tags:
``` C++17
``` c++
inline double Square(double x)
{
return x * x;
}
```
%% Cell type:markdown id: tags:
when this function is called somewhere, the compiler may replace directly the function by the code inside the definition:
%% Cell type:code id: tags:
``` C++17
``` c++
{
Square(5.); // The compiler might substitute 5. * 5. to the actual function call here
}
```
%% Cell type:markdown id: tags:
This behaviour is pretty similar to the often frowned-upon **macros** from C, but the use of `inline` is absolutely not considered a bad practice... provided you have in mind the way it works:
* You have probably notice the conditional in my statements regarding `inline`: the keyword is an _hint_ given to the compiler... that might be followed or not.
* On the syntactic side, `inline` must be provided both in the declaration `and` the definition.
* `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation).
* Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead.
The `Square` example was sound: this is typically the kind of functions that might be inlined.
Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled:
%% Cell type:code id: tags:
``` C++17
``` c++
#define SQUARE(x) ((x) * (x)) // macro
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
double x = 5.;
std::cout << Square(++x) << std::endl;
}
{
double x = 5.;
std::cout << SQUARE(++x) << std::endl;
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 2](./4b-hands-on.ipynb)
%% Cell type:markdown id: tags:
### __EXERCISE 2: Adding a function__
Introduce in the previous program a function called `DisplayPowerOf2Approx()` which takes as argument the actual value to be approximated. Modify the main program to call this function with values 0.65 and 0.35. The main program must be:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
DisplayPowerOf2Approx(.65);
DisplayPowerOf2Approx(.35);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
*Expected result*:
0.65 ~ 1 / 2^1
0.65 ~ 3 / 2^2
0.65 ~ 5 / 2^3
0.65 ~ 10 / 2^4
0.65 ~ 21 / 2^5
0.65 ~ 42 / 2^6
0.65 ~ 83 / 2^7
0.65 ~ 166 / 2^8
0.35 ~ 1 / 2^1
0.35 ~ 1 / 2^2
0.35 ~ 3 / 2^3
0.35 ~ 6 / 2^4
0.35 ~ 11 / 2^5
0.35 ~ 22 / 2^6
0.35 ~ 45 / 2^7
0.35 ~ 90 / 2^8
%% Cell type:markdown id: tags:
### __EXERCISE 3: Compute the approximation__
Add in the `DisplayPowerOf2Approx()` function the display of the approximate value, which is calculated by dividing each numerator by the power of two associated. Be careful, dividing an integer by an integer returns an integer: for instance 3 / 4 is 0. A division returns a real if one of the terms is real: 3. / 4 or `static_cast<double>`(3) / 4 is 0.75.
We will modify the display slightly so that the output looks like:
```
0.65 ~ 0.5 (1 / 2^1)
0.65 ~ 0.75 (3 / 2^2)
0.65 ~ 0.625 (5 / 2^3)
0.65 ~ 0.625 (10 / 2^4)
0.65 ~ 0.65625 (21 / 2^5)
0.65 ~ 0.65625 (42 / 2^6)
0.65 ~ 0.648438 (83 / 2^7)
0.65 ~ 0.648438 (166 / 2^8)
0.35 ~ 0.5 (1 / 2^1)
0.35 ~ 0.25 (1 / 2^2)
0.35 ~ 0.375 (3 / 2^3)
0.35 ~ 0.375 (6 / 2^4)
0.35 ~ 0.34375 (11 / 2^5)
0.35 ~ 0.34375 (22 / 2^6)
0.35 ~ 0.351562 (45 / 2^7)
0.35 ~ 0.351562 (90 / 2^8)
```
%% Cell type:markdown id: tags:
### __EXERCISE 4: Search for the best approximation for a given maximum numerator.__
The larger the numerator and the exponent to the denominator, the more accurate the approximation.
In `DisplayPowerOf2Approx()`, modify the loop so that it looks for the best numerator / exponent pair, without the numerator exceeding a certain maximum value, passed as an argument to the function.
Keep the display only for this best solution, and add in this display the value of the maximum allowed numerator.
Use the following `main()` function to check it works:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
DisplayPowerOf2Approx(15, 0.65);
DisplayPowerOf2Approx(255, 0.65);
DisplayPowerOf2Approx(15, 0.35);
DisplayPowerOf2Approx(255, 0.35);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
Expected result is something like:
```
[With numerator < 15]: 0.65 ~ 0.625 (10 / 2^4)
[With numerator < 255]: 0.65 ~ 0.648438 (166 / 2^8)
[With numerator < 15]: 0.35 ~ 0.34375 (11 / 2^5)
[With numerator < 255]: 0.35 ~ 0.349609 (179 / 2^9)
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
### __EXERCISE 5: Computation of the maximum numerator as a function of the number of bits__
The highest usable value for the numerator depends on the number of bits used to represent this integer.
In `DisplayPowerOf2Approx()` arguments, replace the argument designating the maximum numerator with an argument designating the maximum number of bits and correct the body of the function accordingly, using the `MaxInt()` function given below.
On display, replace the maximum numerator with the number of bits.
%% Cell type:code id: tags:
``` C++17
``` c++
// Declaration
/*!
* \brief Maximum integer that might be represented with `nbits` bits.
*
* \param[in] nbits Number of bits available.
*
* \return Biggest integer that may be represented.
*/
int MaxInt(int nbits);
// Definition
int MaxInt(int nbits)
{
return (TimesPowerOf2(1, nbits) - 1);
}
```
%% Cell type:markdown id: tags:
**WARNING:** If you do not separate declaration and definition explicitly, `MaxInt()` must be located _before_ `DisplayPowerOf2Approx()`.
Use the following `main()` function:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65);
std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
*Expected result*:
```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2)
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4)
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6)
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8)
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3)
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5)
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7)
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9)
```
%% Cell type:markdown id: tags:
### __EXERCISE 6: moving display to a new intermediate function__
Currently, `DisplayPowerOf2Approx()` is in charge of two different operations:
* Computing the values.
* Displaying them on screen.
It is often advisable to give one main functionality to a given function; we will therefore separate here the computation from the display.
Write a new function named `ComputePowerOf2Approx()` that will be called in `DisplayPowerOf2Approx()`.
This new function should:
* Return the floating point approximation.
* Return (with reference parameters) the numerator and the exponent (that are displayed on screen).
Output should remain the same!
**WARNING:** If you do not separate declaration and definition explicitly, `ComputePowerOf2Approx()` must be located _before_ `DisplayPowerOf2Approx()`.
%% Cell type:markdown id: tags:
### __EXERCISE 7: adding error in display__
Add in `DisplayPowerOf2Approx()` the relative error of the approximation, computed by
```
|number - approx| / number
```
presented as a percentage and rounded to the nearest integer.
Absolute value may be computed with `std::fabs`, which is in header file `cmath`.
*Expected result*:
```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Dynamic allocations](./5-DynamicAllocation.ipynb)
%% Cell type:markdown id: tags:
## Introduction
In C++, we can finely control the life cycle of objects and manage the memory allocated to them. This is what makes it possible to create more powerful applications than with many other languages, but it is also the main source of errors in the language. Pointers and dynamic memory management: watch out for danger!
## Stack
The ordinary variables of C++ have a lifetime limited to the current instruction block, whether it is the current function, or an instruction block attached to an `if`, `for` or just independent.
The memory allocated to them is located in an area called a **stack**, and is automatically relieved when exiting the current block using the **last in, first out** principle.
If you want to learn more about memory layout, have a look [here]( https://www.geeksforgeeks.org/memory-layout-of-c-program/).
%% Cell type:code id: tags:
``` C++17
``` c++
{
{
int a { 5 };
double b { 7.4 };
} // at the end of this block, b is released first and then a - but 99.99 % of the time you shouldn't care
// about that order!
// a and b are not available here
}
```
%% Cell type:markdown id: tags:
There are few limitations with the stack:
* The number of memory you can allocate on the stack is rather limited. On a current POSIX OS the order of magnitude is ~ 8 MB (on Unix type `ulimit -s` in a terminal to get this information). If you allocate more you will get a **stack overflow** (and now you know why the [most popular developers forum](https://stackoverflow.com/) is named this way!)
* The information is very local; you can't use it elsewhere. If you pass the variable as argument in a function for instance a copy is made (or if you're using a reference or a pointer you have to be sure all is done when the block is exited!)
* Stack information must be known at compile time: if you're allocating an array on the stack you must know its size beforehand.
%% Cell type:markdown id: tags:
## Heap and free store
You can in fact also explicitly place a variable in another memory area called **heap** or **free store**; doing so overcomes the stack limitations mentioned above.
This is done by calling the `new` operator, which reserves the memory and returns its address, so that the user can store it _with a pointer_.
The **heap** is independent of the **stack** and the variable thus created exists as long as the `delete` operator is not explicitly called. The creation and destruction of this type of variable is the responsibility of the programmer.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int* n = new int(5); // variable created on the heap and initialized with value 5.
std::cout << *n << std::endl;
delete n; // deletion must be explicitly called; if not there is a memory leak!
}
```
%% Cell type:markdown id: tags:
What is especially tricky is that:
* Creating and destroying can be done in places very disconnected in your program.
* You must ensure that whatever the runtime path used in your program each variable allocated on the heap:
- is destroyed (otherwise you get a **memory leak**)
- is only destroyed once (or your program will likely crash with a message about **double deletion**).
In sophisticated programs, this could lead in serious and tedious bookkeeping to ensure all variables are properly handled, even if tools such as [Valgrind](http://www.valgrind.org/) or [Address sanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) may help to find out those you will probably have forgotten somewhere along the way.
To be honest, C++ gets quite a bad name due to this tedious memory handling; fortunately the RAII idiom provides a neat way to automate nicely memory management (which we'll study [later](../5-UsefulConceptsAndSTL/2-RAII.ipynb)) and some vocal critics on forums that regret the lack of [garbage collection](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) might actually not be aware of this fundamental (from my point of view at least) idiom.
%% Cell type:markdown id: tags:
### Free store?
**Free store** is very similar in functionality to the **heap** (to the point I had to [check the difference](https://stackoverflow.com/questions/1350819/c-free-store-vs-heap) before writing this...) , and more often than not one word might be used as the other. If you want to be pedantic:
* When memory is handled by `new`/`delete`, you should talk about **free store**.
* When memory is handled by `malloc`/`free` (the C functions), you should talk about **heap**.
Pedantry aside, the important thing to know is to never mix both syntax: if you allocate memory by `new` don't use `free` to relieve it.
%% Cell type:markdown id: tags:
## Arrays on heap
If you want to init an array which size you do not know at compile time or that might overflow the stack, you may to do with `new` syntax mixed with `[]`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <random>
int* throw_dice(std::size_t ndigit) {
// Don't bother much here - this is lifted from https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution
std::random_device rd; // a seed source for the random number engine
std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> distrib(1, 6);
int* dice_result = new int[ndigit];
for (std::size_t i = 0; i < ndigit; ++i)
dice_result[i] = distrib(gen);
return dice_result;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
auto Ndice = 5ul;
int* throw_5_dices = throw_dice(Ndice);
int* throw_5_dice = throw_dice(Ndice);
for (std::size_t i = 0; i < Ndice; ++i)
std::cout << throw_5_dices[i] << std::endl;
std::cout << throw_5_dice[i] << std::endl;
delete[] throw_5_dices;
delete[] throw_5_dice;
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
Ndice = 3;
int* throw_7_dices = throw_dice(Ndice);
int* throw_7_dice = throw_dice(Ndice);
for (std::size_t i = 0; i < Ndice; ++i)
std::cout << throw_7_dices[i] << std::endl;
std::cout << throw_7_dice[i] << std::endl;
delete[] throw_7_dices;
delete[] throw_7_dice;
```
%% Cell type:markdown id: tags:
Please notice that:
* No value can be assigned in construction: you must first allocate the memory for the array and only in a second time fill it.
* A `[]` **must** be added to the **delete** instruction to indicate to the compiler this is actually an array that is destroyed.
In fact, my advice would be to avoid entirely to deal directly with such arrays and use containers from the standard library such as `std::vector`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <vector>
{
std::vector<int> pi_first_five_digits { 3, 1, 4, 1, 5 };
}
```
%% Cell type:markdown id: tags:
that does the exact same job in a shorter way and is much more secure to use (spoiler: `std::vector` is built upon the RAII idiom mentioned briefly in this notebook).
We shall see `std::vector` more deeply [later](../5-UsefulConceptsAndSTL/3-Containers.ipynb) but will nonetheless use it before this as it is a rather elementary brick in most C++ codes.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Input and output streams](./6-Streams.ipynb)
%% Cell type:markdown id: tags:
## Predefined streams
The standard C++ includes an input/output library that specifies a common interface for all data exchanges with the outside world, based in particular on the insertion `<<` and extraction `>>` operators.
### `std::cout`
We have already dealt profusely with `std::cout` which provide the link to the Unix channel `stdout`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
std::cout << "Hello world!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### `std::cerr`
There is also `std::cerr`, which is related to Unix `stderr`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
int n = -4;
if (n < 0)
std::cerr << "Positive or null value expected!" << std::endl;
}
```
%% Cell type:markdown id: tags:
### `std:cin`
And finally `std::cin`, related to Unix channel `stdin`. Line crossings are ignored (assimilated to spaces and tabs).
**WARNING** This works only with a recent version of Xeus-cling.
%% Cell type:code id: tags:
``` C++17
#include <random>
``` c++
%%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system.
#include <cstddef>
#include <iostream>
#include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
std::random_device rd; // Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen);
int guess = -1;
while (guess != hidden)
{
std::cout << "Find the value between 0 and 100: ";
std::cin >> guess;
if (guess > hidden)
std::cout << " Too high!" << std::endl;
else if (guess < hidden)
std::cout << " Too low!" << std::endl;
}
std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
`std::cin` is a bit more tricky to use than the others, as the risk the operation fails is really higher. For instance, if you give a string in the code above it will become crazy and keep printing the same message "Too high!" or "Too low!" (be ready to restart the kernel...). The following code fixes this:
%% Cell type:code id: tags:
``` C++17
#include <random>
#include <iostream>
``` c++
%%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system.
#include <cstddef>
#include <iostream>
#include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen);
int guess = -1;
while (guess != hidden)
{
do
{
if (!std::cin)
{
std::cin.clear(); // clear the states of std::cin, putting it back to `goodbit`.
std::cin.ignore(10000, '\n'); // clean-up what might remain in std::cin before using it again.
}
std::cout << "Find the value between 0 and 100: ";
std::cin >> guess;
} while (!std::cin);
if (guess > hidden)
std::cout << " Too high!" << std::endl;
else if (guess < hidden)
std::cout << " Too low!" << std::endl;
}
std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
If you want to learn more about `std::cin`, you might want to look at [this post](https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input) on StackOverflow.
If you need to use it extensively, you should look more deeply the behaviour of the bit flags (`goodbit`, `badbit`, `failbit`, `eofbit`).
%% Cell type:markdown id: tags:
## Input/output with files
The same syntax with operators `<<` and `>>` may be used to interact with files; the streams are built with `std::ofstream` for an output stream and `std::ifstream` for an input stream.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <fstream> // for std::ifstream and std::ofstream
#include <iostream>
{
std::ofstream out("File.tmp");
out << 5 << std::endl;
out << -7 << std::endl;
out << 9 << std::endl;
out.close(); // file is written on disk when closed; automatically done when `out` gets out of scope otherwise
std::ifstream in("File.tmp");
int value;
while (in >> value)
std::cout << value << std::endl;
}
```
%% Cell type:markdown id: tags:
### `getline()`
When reading a file, if you want to interpret it line by line you should also consider `getline()`; this function may get a third argument to choose which separator to use (`\n` by default).
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <fstream>
#include <string>
{
std::ifstream in("File.tmp"); // assumes previous cell has been played recently!
std::string line;
while (getline(in, line))
std::cout << line << std::endl;
}
```
%% Cell type:markdown id: tags:
## `ostream` and `istream`
If you want to devise a function that may take as argument either a `std::cout` or a `std::ofstream`, you should use a `std::ostream` (we'll study [later](../2-ObjectProgramming/6-inheritance.ipynb) why this works but just take my word for now):
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintOnStream(std::ostream& out)
{
out << "Printed on the chosen stream!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
PrintOnStream(std::cout);
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <string>
#include <fstream>
{
std::ofstream out("test_stream.txt");
PrintOnStream(out);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
// Read the content of the line previously written.
std::ifstream in("test_stream.txt");
std::string line;
getline(in, line);
std::cout << line << std::endl;
}
```
%% Cell type:markdown id: tags:
## Binary files
Exactly as in C, you may want to write directly your output in binary format rather than in ascii format by specifying `std::ios::binary` as second argument of `std::ofstream` or `std::ifstream`.
It may be combined with others with `|` operator (here `std::ios::app` which tells to append the new content at the end the file if it already exists - without this cue the default behaviour is to overwrite pre-existing `test_stream.binary` file).
%% Cell type:code id: tags:
``` c++
#include <fstream>
{
std::ofstream out("test_stream.binary", std::ios::binary | std::ios::app);
}
```
%% Cell type:markdown id: tags:
Using binary is *much* faster than ascii; if you want to write lots of data (typically in HPC) you should probably consider it.
Drawbacks are:
- Files written this way are less portable.
- Files can't be read directly by a human.
- Writing and reading such files requires more handiwork by the developer.
Still, it is an option that should be on the table as I/O might really be a bottleneck in runtime.
For more details see [the documentation on base class for all I/O stream classes](https://en.cppreference.com/w/cpp/io/ios_base) - fmtflags are also interesting, but a bit to deep for the training.
%% Cell type:markdown id: tags:
## Conversion
Stream syntax was until C++ 11 the only way to convert:
- A string into a number with `std::istringstream`
- A number into a string with `std::ostringstream`; the `str()` method returns the content as a `std::string`.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <sstream> // for std::ostringstream and std::istringstream
#include <string>
{
std::string number_as_string = "657";
int number;
std::istringstream iconv(number_as_string);
iconv >> number;
std::cout << "Number + 1 = " << number + 1 << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <sstream> // for std::ostringstream and std::istringstream
#include <string>
{
int number = 657;
std::ostringstream oconv;
oconv << "The number is " << number;
std::cout << oconv.str() << std::endl;
}
```
%% Cell type:markdown id: tags:
To reuse a `std::ostringstream`, you must set its content to an empty string with an overloaded `str()`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <sstream> // for std::ostringstream and std::istringstream
#include <string>
{
int number = 657;
std::ostringstream oconv;
oconv << "The number is " << number;
std::cout << oconv.str() << std::endl;
oconv.str(""); // reset oconv
oconv << "My new content is now there!";
std::cout << oconv.str() << std::endl;
}
```
%% Cell type:markdown id: tags:
Of course as for `std::cin` you may check the state of the object is still valid - if conversion is incorrect it won't!:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <sstream> // for std::ostringstream and std::istringstream
#include <string>
{
std::string number_as_string = "abd";
int number;
std::istringstream iconv(number_as_string);
iconv >> number; // invalid conversion!
if (!iconv)
std::cerr << "Invalid string!" << std::endl;
else
std::cout << "Number + 1 = " << number + 1 << std::endl;
}
```
%% Cell type:markdown id: tags:
In C++ 11, `std::to_string()` and the [`stoi` (and similar functions for long)](https://en.cppreference.com/w/cpp/string/basic_string/stol) were introduced to provide similar functionality with a more direct syntax:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <string>
{
int number = 657;
std::string int_to_string("Number is ");
int_to_string += std::to_string(number);
std::cout << int_to_string << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <string>
{
std::string number_as_string = "4557";
int number = std::stoi(number_as_string);
std::cout << "Number is " << number << std::endl;
}
```
%% Cell type:markdown id: tags:
It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well...
C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support) (still true in 2024...)
%% Cell type:markdown id: tags:
## Formatting and manipulators
You may act upon the exact formatting of the output.
I'll be honest: it's not what is the most refined tool in the C++ library, and you may long for the simplicity and power of something like Python (or even C `printf`, which is much simpler to use while being a mess under the hood...).
Once again `std::format` from C++ 20 should be a game changer here!
The difficulty is that some settings apply only to the next entry onto the stream (`width` here), while others change the behaviour permanently (until told otherwise of course, e.g. `precision` here). Here are few examples of these syntaxes:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout.precision(2); // number of decimal digits
std::cout.width(8) ;
std::cout << 1.237 ;
std::cout.width(8) ;
std::cout << 100.1245 ;
std::cout.width(8) ;
std::cout << '\n' ;
std::cout.width(8) ;
std::cout << 1.5774e-2 ;
std::cout.width(8) ;
std::cout << 12. << '\n' ;
}
```
%% Cell type:markdown id: tags:
**Manipulators** provide a shorter syntax to add some of the properties as the `width` or the `precision`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <iomanip> // for std::setprecision
{
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout << std::setprecision(2) << std::setw(8) << 1.237;
std::cout << std::setw(8) << 100.1245;
std::cout << '\n';
std::cout << std::setw(8) << 1.5774e-2;
std::cout << std::setw(8) << 12. << '\n';
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 3](./7b-hands-on.ipynb)
%% Cell type:markdown id: tags:
### __EXERCISE 8: Multiplication by an integer__
Write the `Multiply()` function that calculates the product of an approximated real by an integer coefficient and returns an integer.
*Reminder*: a real is approximated by `numerator / 2^exponent`; your function should rely upon `ComputePowerOf2Approx()` and `TimesPowerOf2()` functions.
The arguments of `Multiply()` are the maximum number of bits for approximation, the real and the integer coefficient.
The new main will be:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65) ;
std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35) ;
std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits)
{
double exact = 0.65 * 3515;
int rounded = RoundAsInt(exact);
int approx = Multiply(nbits, 0.65, 3515);
std::cout << "[With " << nbits << " bits]: 0.65 * 3515 = "
<< rounded << " ~ " << approx << std::endl;
}
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
_Expected result:_
```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 = 2285 ~ 1757
[With 2 bits]: 0.65 * 3515 = 2285 ~ 2636
[With 3 bits]: 0.65 * 3515 = 2285 ~ 2196
[With 4 bits]: 0.65 * 3515 = 2285 ~ 2196
[With 5 bits]: 0.65 * 3515 = 2285 ~ 2306
[With 6 bits]: 0.65 * 3515 = 2285 ~ 2306
[With 7 bits]: 0.65 * 3515 = 2285 ~ 2279
[With 8 bits]: 0.65 * 3515 = 2285 ~ 2279
```
%% Cell type:markdown id: tags:
### __EXERCISE 9: display sum of two Multiply__
Write a `DisplaySumOfMultiply` function which will write the computation of the sum of two approximated real numbers that have each been multiplied by a different integer coefficient.
The function is expected to compute and display the following:
$$
\displaystyle r_1 \cdot c_1 + r_2 \cdot c_2 \simeq (a_1 \cdot 2^{-b_1}) \cdot c_1 + (a_2 \cdot 2^{-b_2}) \cdot c_2
$$
This function will take 5 arguments:
* The number of bits to use.
* Two real values.
* Their associated coefficients.
New main will look like:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.65);
std::cout << std::endl;
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(nbits, 0.35);
std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits)
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
*Expected result*:
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968
%% Cell type:markdown id: tags:
### __EXERCISE 10: print error in `DisplaySumOfMultiply()`__
Modify slightly the function defined above to add display of the error; we will express it over 1000 (see exercise 7 which was roughly the same!)
*Expected result*:
```
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
```
%% Cell type:markdown id: tags:
### [optional] __EXERCISE 11: write in output file__
Modify the program so that the `DisplayPowerOf2Approx` and `DisplaySumOfMultiply` functions take an additional argument: the output stream to which the content should be written.
The following `main()` which writes part of the outputs in a file should work:
%% Cell type:code id: tags:
``` C++17
``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
std::ofstream out("/tmp/approx_0.65.txt");
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(out, nbits, 0.65);
for (int nbits = 2; nbits <= 8; nbits += 2)
DisplayPowerOf2Approx(std::cout, nbits, 0.35);
std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits)
DisplaySumOfMultiply(std::cout, nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
*Expected result*:
```
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
```
%% Cell type:markdown id: tags:
and `cat /tmp/approx_0.65.txt` should return:
```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
```
%% Cell type:markdown id: tags:
### [optional] __EXERCISE 12: function pointers__
Create a `Loop()` function that takes as an argument :
* The output stream
* An initial number of bits
* A final number of bits
* An increment to be applied to the number of bits
* A pointer to a function to be executed for each number of bits
You will need the following intermediate function to be able to use them in `Loop()` (as a specific signature is expected):
%% Cell type:code id: tags:
``` C++17
``` c++
// Declarations
void Display_065(std::ostream& out, int nbits);
void Display_035(std::ostream& out, int nbits);
void Display_065_3515_035_4832(std::ostream& out, int nbits);
// Definitions
void Display_065(std::ostream& out, int nbits)
{
DisplayPowerOf2Approx(out, nbits, 0.65);
}
void Display_035(std::ostream& out, int nbits)
{
DisplayPowerOf2Approx(out, nbits, 0.35);
}
void Display_065_3515_035_4832(std::ostream& out, int nbits)
{
DisplaySumOfMultiply(out, nbits, 0.65, 3515, 0.35, 4832);
}
// Main
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
std::ofstream out("/tmp/approx_0.65.txt");
Loop(out, 2, 8, 2, Display_065);
Loop(std::cout, 2, 8, 2, Display_035);
std::cout << std::endl;
Loop(std::cout, 1, 8, 1, Display_065_3515_035_4832);
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Procedural programming](./0-main.ipynb) - [Static and constexpr](./7-StaticAndConstexpr.ipynb)
%% Cell type:markdown id: tags:
## Static keyword
%% Cell type:markdown id: tags:
`static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.
A reply to this [StackOverflow question](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) gives a rather good summary of what `static` means in C (we shall see [later](../2-ObjectProgramming/5-static.ipynb) a C++-only keyword that unfortunately share the same moniker):
* Static defined local variables do not lose their value between function calls. In other words they are global variables, but scoped to the local function they are defined in.
* Static global variables are not visible outside of the C file they are defined in.
* Static functions are not visible outside of the C file they are defined in.
Only the first one is really relevant in C++, as for variables that should be accessible only in the current file C++ provides a concept of his own: the [**unnamed namespace**](../6-InRealEnvironment/5-Namespace.ipynb#Unnamed-namespace).
Let's see this first case in action:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void FunctionWithStatic()
{
static int n = 0; // This initialisation occurs only at first call
// But `n` is not destroyed when the end bracket is reached and remains available
// in subsequent calls. However, `n` is available only inside this function.
std::cout << "The function has been called " << ++n << " times." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
for (int i = 0; i < 5; ++i)
FunctionWithStatic();
}
```
%% Cell type:markdown id: tags:
It might be used for instance if you need to initialize something on the very first call of a function:
%% Cell type:code id: tags:
``` C++17
``` c++
// Pseudo-code
void FunctionWithStatic()
{
static bool is_first_call = true;
if (is_first_call)
{
// Init stuff here on first call only, for instance something that requires heavy computation.
// ...
is_first_call = false;
}
// ... code executed at each call
}
```
%% Cell type:markdown id: tags:
## Constexpr
We've seen the allocation of an array on the stack follows this syntax:
%% Cell type:code id: tags:
``` C++17
``` c++
int array[5ul];
```
%% Cell type:markdown id: tags:
where the size of the array is available at compile time (if not you have to use an array allocated on the heap at runtime).
Now imagine we want to init such an array with a size that results from a computation; let's say a Fibonacci series:
%% Cell type:code id: tags:
``` C++17
// Recursive function - Xeus cling may not appreciate if you call this cell several times.
``` c++
// Recursive function - kernel may not appreciate if you call this cell several times.
auto Fibonacci (std::size_t n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return Fibonacci(n-1) + Fibonacci(n-2);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
std::cout << Fibonacci(5) << std::endl;
std::cout << Fibonacci(10) << std::endl;
```
%% Cell type:code id: tags:
``` C++17
``` c++
double array[Fibonacci(5)]; // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
This doesn't seem outlandish: a computation is involved and this computation happens at runtime - even if in fact all the required elements to perform it were available at compile time (there were for instance no argument read from command line involved).
In C++ 03, you had two choices to resolve this
- Using a macro...
- Or using [metaprogramming](../4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).
C++ introduced the keyword `constexpr`, which indicates something happens at compile time. It might be used for just a variable, or for more complicated construct such as functions or classes. The only requirement is that all the subparts used are themselves `constexpr`.
%% Cell type:code id: tags:
``` C++17
``` c++
constexpr auto FibonacciConstexpr (std::size_t n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return FibonacciConstexpr(n-1) + FibonacciConstexpr(n-2);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
double array[FibonacciConstexpr(5)]; // Ok!
```
%% Cell type:markdown id: tags:
`constexpr` function may also be used as runtime function, but in this case their results can't of course be used at compile time.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
int i = 7;
++i; // i is by no stretch a compile time variable!
std::cout << FibonacciConstexpr(i) << std::endl;
```
%% Cell type:markdown id: tags:
`constexpr` becomes increasingly powerful over time:
- The function `FibonacciConstexpr` above does not in fact work before C++ 14: this version of the standard introduced the possibility to provide several `return` in a `constexpr` function.
- `constexpr` is added wherever possible in the STL. This is still a work in progress: if you look for instance the [CppReference page](https://en.cppreference.com/w/cpp/algorithm/count) for `std::count` algorithm, you will see this algorithm becomes `constexpr` in C++ 20.
We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr).
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
......@@ -36,16 +36,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Introduction to the concept of object](./1-Introduction.ipynb)
%% Cell type:markdown id: tags:
## Motivation
%% Cell type:markdown id: tags:
Sometimes, there are variables that are bound to be initialized and used together. Let's consider the coordinates of a vector in a three-dimensional space:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
#include <cmath> // For std::sqrt
double Norm(double v_x, double v_y, double v_z)
{
return std::sqrt( v_x * v_x + v_y * v_y + v_z * v_z );
}
{
double v1_x, v1_y, v1_z;
v1_x = 1.;
v1_y = 5.;
v1_z = -2.;
std::cout << Norm(v1_x, v1_y, v1_z) << std::endl;
double v2_x, v2_y, v2_z;
v2_x = 2.;
v2_y = 2.;
v2_z = 4.;
std::cout << Norm(v2_x, v2_y, v2_z) << std::endl;
}
```
%% Cell type:markdown id: tags:
The code above is completely oblivious of the close relationship between `x`, `y` and `z`, and for instance the `Norm` function takes three distinct arguments.
This is not just an inconveniency: this can lead to mistake if there is an error in the variables passed:
%% Cell type:code id: tags:
``` C++17
``` c++
{
double v1_x, v1_y, v1_z;
v1_x = 1.;
v1_y = 5.;
v1_z = -2.;
double v2_x, v2_y, v2_z;
v2_x = 2.;
v2_y = 2.;
v2_z = 4.;
const double norm1 = Norm(v1_x, v1_y, v2_z); // probably not what was intended, but the program
// has no way to figure out something is fishy!
}
```
%% Cell type:markdown id: tags:
## The C response: the `struct`
C introduced the `struct` to be able to group nicely data together and limit the risk I exposed above:
%% Cell type:code id: tags:
``` C++17
``` c++
struct Vector
{
double x;
double y;
double z;
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
double Norm(Vector v)
{
return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
{
Vector v1;
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
std::cout << Norm(v1) << std::endl;
Vector v2;
v2.x = 2.;
v2.y = 2.;
v2.z = 4.;
std::cout << Norm(v2) << std::endl;
}
```
%% Cell type:markdown id: tags:
Calling `Norm` is now both more elegant (only one argument) and less dangerous (I can't mix by mistake coordinates from different objects).
Let's introduce at this point a bit of vocabulary:
- `x`, `y` and `z` in the structure are called **member variables** or **data attributes** (often shorten as **attributes** even if in a class this is actually not completely proper). On a side note: some C++ purists will be adamant only **member variables** should be used; but I rather use **data attributes** which is the term preferred in many others object programming languages.
- `Vector` is a **struct**, which is a somewhat simplified **class** (we will explain the difference when we'll introduce classes).
- `v1` and `v2` are **objects**.
Let's also highlight the `.` syntax which allows to access the attributes of an object (e.g `v1.x`).
%% Cell type:markdown id: tags:
### The semicolon at the end of a `struct`
This comes historically from the C, where a `struct` could be defined and initialized at the same time (or should - Xeus-cling doesn't manage it... As usual you may check a full-fledged compiler accepts it [@Coliru](http://coliru.stacked-crooked.com/a/3b77606ea8082485)):
This comes historically from the C, where a `struct` could be defined and initialized at the same time:
%% Cell type:code id: tags:
``` C++17
// Xeus-cling issue (at least circa September 2022 and still there in February 2024)
``` c++
%%cppmagics clang
// < Following code isn't supported by our kernel, so we're using native compiler instead.
#include <cstdlib>
int main()
{
struct VectorAndInstantiate
{
double x;
double y;
double z;
} v1; // Here the struct is declared and at the same time an object v1 is created
struct VectorAndInstantiate
{
double x;
double y;
double z;
} v1; // Here the struct is declared and at the same time an object v1 is created
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
This is absolutely **not** encouraged in C++, but it may help you to remember always closing a `struct` (or later a `class`) with a semicolon.
%% Cell type:markdown id: tags:
## Passing a struct to a function
In the `norm` function above, we passed as argument an object of `Vector` type by value. When we introduced functions, we saw there were three ways to pass an argument:
* By value.
* By reference.
* By pointers.
I didn't mention there the copy cost of a pass-by-value: copying a plain old data (POD) type such as an `int` or a `double` is actually cheap, and is recommended over a reference. But the story is not the same for an object: the cost of copying the object in the case of a pass-by-value may actually be quite high (imagine if there were an array with thousands of `double` values inside for instance) - and that's supposing the object is copyable (but we're not quite ready yet to deal with [that aspect](../3-Operators/4-CanonicalForm.ipynb#Uncopyable-class)).
### Pass-by-const-reference
So most of the time it is advised to pass arguments by reference, often along a `const` qualifier if the object is not to be modified by the function:
%% Cell type:code id: tags:
``` C++17
``` c++
double NormWithoutCopy(const Vector& v)
{
return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
{
Vector v1;
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
std::cout << NormWithoutCopy(v1) << std::endl;
}
```
%% Cell type:markdown id: tags:
### Pass-by-pointer
Of course, if for some reason you prefer to use pointers it is also possible:
%% Cell type:code id: tags:
``` C++17
``` c++
double Norm(const Vector* const v) // can keep the name here: no possible ambiguity
{
return std::sqrt((*v).x * (*v).x + (*v).y * (*v).y + (*v).z * (*v).z);
}
{
Vector v1;
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
std::cout << Norm(&v1) << std::endl;
}
```
%% Cell type:markdown id: tags:
This is more than little verbosy, so a shortcut has been introduced; `->` means you dereference a pointer and then calls the attribute:
%% Cell type:code id: tags:
``` C++17
``` c++
double NormWithPointerShortcut(const Vector* const v)
{
return std::sqrt(v->x * v->x + v->y * v->y + v->z * v->z);
}
{
Vector v1;
v1.x = 1.;
v1.y = 5.;
v1.z = -2.;
std::cout << NormWithPointerShortcut(&v1) << std::endl;
}
```
%% Cell type:markdown id: tags:
## Initialization of objects
So far, we have improved the way the `Norm` function is called, but the initialization of a vector is still a bit tedious. Let's wrap up a function to ease that:
%% Cell type:code id: tags:
``` C++17
``` c++
void Init(Vector& v, double x, double y, double z)
{
v.x = x;
v.y = y;
v.z = z;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
Vector v1;
Init(v1, 1., 5., -2.);
std::cout << "Norm = " << Norm(v1) << std::endl;
}
```
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
......@@ -60,16 +60,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb)
%% Cell type:markdown id: tags:
## Member functions
The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers).
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...)
One of the idea that was missing with original C `struct` was the possibility to add as well member functions:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <cmath>
struct Vector
{
double x;
double y;
double z;
void Init(double x, double y, double z)
{
this->x = x;
this->y = y;
this->z = z;
}
double Norm()
{
return std::sqrt(x * x + y * y + z * z);
}
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
Vector v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
Let's do a bit of taxonomy here:
- `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here.
- **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method.
- **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes.
**WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data attributes have to be declared within the `struct` brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding.
%% Cell type:markdown id: tags:
## The `this` keyword
The `this->` may have puzzled you: it is a keyword to refer to the current object (very akin to Python `self`).
In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely.
An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <cmath>
struct Vector2
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z)
{
x_ = x; // no need to `this` here as there is no ambiguity between data attribute name and parameter name
y_ = y;
z_ = z;
}
double Norm()
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
Vector2 v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...)
**Note:** another popular convention to name data attributes is to prefix with `m_` (e.g. `double m_x;` instead of `double x_` above). As most conventions, you should either use the one defined for the code on which you work or choose the one the more appropriate for you and stick with it if you're the decision maker on it.
%% Cell type:markdown id: tags:
## Separating declaration and definition
We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have:
- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)):
%% Cell type:code id: tags:
``` C++17
``` c++
struct Vector3
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z);
double Norm();
};
```
%% Cell type:markdown id: tags:
- On another side the definition, usually in a source file which includes the header file:
%% Cell type:code id: tags:
``` C++17
``` c++
void Vector3::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
double Vector3::Norm()
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
Vector3 v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
Please notice the `::` syntax which specifies the class for which the implementation is provided.
%% Cell type:markdown id: tags:
## Const methods
Are we happy here with what we have so far? Unfortunately, not quite...
If we define a simple free function that print the norm of a `Vector3`:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintNorm(const Vector3& v)
{
std::cout << v.Norm() << std::endl; // COMPILATION ERROR
}
```
%% Cell type:markdown id: tags:
... we see that doesn't compile. So what is happening?
The issue here is that the function `PrintNorm` takes as argument a constant reference to a `Vector` object, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const:
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintNormNoConst(Vector3& v) // BAD IDEA!
{
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
Vector3 v;
v.Init(5., 6., -4.2);
PrintNormNoConst(v);
}
```
%% Cell type:markdown id: tags:
Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong.
The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned.
The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object:
%% Cell type:code id: tags:
``` C++17
``` c++
struct Vector4
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z);
double Norm() const; // notice the additional keyword!
void DontPutConstEverywhere() const;
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
void Vector4::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
double Vector4::Norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:markdown id: tags:
Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell.
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
void PrintNorm(const Vector4& v)
{
std::cout << v.Norm() << std::endl;
}
{
Vector4 v;
v.Init(5., 6., -4.2);
PrintNorm(v);
}
```
%% Cell type:markdown id: tags:
Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and **SHOULD BE** very good at this!):
%% Cell type:code id: tags:
``` C++17
``` c++
void Vector4::DontPutConstEverywhere() const
{
x_ = 0.; // ERROR!
}
```
%% Cell type:markdown id: tags:
### `mutable` keyword
Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more specific attribute. You may in this case use the `mutable` keyword when defining this attribute:
%% Cell type:code id: tags:
``` C++17
``` c++
struct Vector5
{
double x_;
double y_;
double z_;
mutable unsigned int Nnorm_calls_;
void Init(double x, double y, double z);
double Norm() const;
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
void Vector5::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
Nnorm_calls_ = 0u;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
double Vector5::Norm() const
{
++Nnorm_calls_;
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
{
Vector5 v;
v.Init(5., 6., -4.2);
for (int i = 0; i < 5; ++i)
v.Norm();
std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl;
}
```
%% Cell type:markdown id: tags:
I must stress again that you should use this in a **last resort**!
For my part, I have used this in only two contexts:
* Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods.
* For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial.
%% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md)
......
......@@ -53,16 +53,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......
%% 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++17
``` 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++17
``` 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++17
``` c++
%%cppmagics 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++17
``` c++
double Vector::Norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` 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++17
``` 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++17
``` 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++17
``` c++
struct First
{
};
{ };
```
%% Cell type:code id: tags:
``` C++17
``` c++
struct Second
{
const First& first_;
Second(const First& first, int value);
const int value_;
};
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` 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++17
``` c++
struct PoorlyDefinedVector
{
double x_;
double y_;
PoorlyDefinedVector(double x, double y);
};
```
%% Cell type:code id: tags:
``` C++17
``` 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)). You may check the very example [in Coliru](https://coliru.stacked-crooked.com/a/f783f09ff4395aa1); both clang++ and g++ provide an adequate warning.
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++
%%cppmagics 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++17
``` 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++17
``` 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++17
``` 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++17
``` c++
Vector2::Vector2(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` 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++17
``` c++
struct ClassWithoutConstructor
{ };
```
%% Cell type:code id: tags:
``` C++17
``` c++
{
ClassWithoutConstructor my_object;
}
```
%% Cell type:code id: tags:
``` C++17
``` c++
struct ClassWithConstructorWithArg
{
ClassWithConstructorWithArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` c++
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` c++
struct BadlyInitialized
{
int a_;
};
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` 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++17
``` c++
SafeClass::SafeClass(int new_value)
: a_(new_value)
{ }
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` c++
struct ClassWithIntConstructor
{
ClassWithIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` c++
struct ClassWithExplicitIntConstructor
{
explicit ClassWithExplicitIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
``` c++
#include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
``` 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++17
``` 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++17
``` 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++17
``` 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++17
``` 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++17
``` 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)
......
......@@ -50,16 +50,15 @@
],
"metadata": {
"kernelspec": {
"display_name": "C++17",
"language": "C++17",
"name": "xcpp17"
"display_name": "Cppyy",
"language": "c++",
"name": "cppyy"
},
"language_info": {
"codemirror_mode": "text/x-c++src",
"codemirror_mode": "c++",
"file_extension": ".cpp",
"mimetype": "text/x-c++src",
"name": "c++",
"version": "17"
"name": "c++"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
......