Alexandre Hamez
alexandre.hamez@gmail.com
C++ 11/14
PRÉSENTATION GÉNÉRALE
C++11/14
3
• Nouveau langage
• Possiblement des gains de performance juste en recompilant
• rvalues references, move semantics
• Quelques incompatibilités avec le standard précédent, mais bien
souvent pour plus de sécurité ou de performances
• ex. un destructeur ne peut plus lever d’exception par défaut
(ce qui était déjà une mauvaise chose)
• ex. règles de conversion plus strictes dans l’initialisation
d’agrégats int tab[] = {1.0}; // compilation error
4
ranged-base for loop
auto
constexpr
static_assert
rvalues references
enum class
uniform initialization
noexcept
variadic templates
relaxed POD definition
nullptr
unrestricted unions
lambdas
decltype
trailing return type
extern template
final
override
>>
user-defined literals
alignas
alignof
5
shared_ptr
unordered_map
mutex
thread
atomic
regex
unique_ptr
bind
reference_wrapper
unordered_set
tuple
chrono
random
forward
forward_list
function
enable_if
result_of
move
array
...
Compilateurs
6
• Visual Studio 2013 (incomplet)
‣ http://msdn.microsoft.com/en-us/library/hh567368.aspx
‣ plus gros manque : constexpr
• Clang ≥ 3.3
‣ http://clang.llvm.org/cxx_status.html
• GCC ≥ 4.8
‣ https://gcc.gnu.org/projects/cxx0x.html
7
http://en.cppreference.com
C++11/14
Boucles simplifiées
9
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
for ( std::vector<int>::const_iterator cit = v.begin()
; cit != v.end(); ++cit)
{
std::cout << *cit << ',';
}
}
À l’ancienne : utilisation des itérateurs
Boucles simplifiées
10
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
for (int i : v)
{
std::cout << i << ',';
}
}
Syntaxe simplifiée
Boucles simplifiées
10
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
for (int i : v)
{
std::cout << i << ',';
}
}
Syntaxe simplifiée
écriture naturelle
Boucles simplifiées
11
{
typedef std::vector<int>::const_iterator cit_t;
for ( cit_t __begin = v.begin(), __end = v.end()
; __begin != __end; ++__begin)
{
int i = *__begin;
std::cout << i << ',';
}
}
sous le capot :
Boucles simplifiées
12
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (std::map<int, int>::value_type key_value : m)
{
std::cout << key_value.first << ',' << key_value.second;
}
n’importe quel conteneur fournissant des itérateurs
Boucles simplifiées
12
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (std::map<int, int>::value_type key_value : m)
{
std::cout << key_value.first << ',' << key_value.second;
}
n’importe quel conteneur fournissant des itérateurs
pas de ‘&’ →copie
Boucles simplifiées
13
const int tab[] = {1,2,3};
for (int x : tab)
{
std::cout << x << 'n';
}
tableaux C
Boucles simplifiées
14
• En résumé, fonctionne pour
‣ les tableaux C
‣ tous les types qui fournissent
- les fonctions membres begin(), end()
- les fonctions libres begin(type), end(type)
auto
15
#include <map>
int main()
{
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (std::pair<int, int>& key_value : m)
{
// do something with key_value
}
}
où est le problème?
auto
16
#include <map>
int main()
{
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (std::pair<const int, int>& key_value : m)
{
// ...
}
}
où est le problème?
auto
16
#include <map>
int main()
{
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (std::pair<const int, int>& key_value : m)
{
// ...
}
}
où est le problème?
la clef dans une std::map est
constante
auto
17
#include <map>
int main()
{
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (auto& key_value : m)
{
// ...
}
}
auto
17
#include <map>
int main()
{
std::map<int, int> m;
m.insert(std::make_pair(1,1));
for (auto& key_value : m)
{
// ...
}
}
le compilateur fait le travail pour
nous : plus d’erreur possible
18
auto
#include <map>
int main()
{
auto m = std::map<int, int>();
m.insert(std::make_pair(1,1));
for (auto& key_value : m)
{
// do something with key_value
}
}
18
auto
#include <map>
int main()
{
auto m = std::map<int, int>();
m.insert(std::make_pair(1,1));
for (auto& key_value : m)
{
// do something with key_value
}
}
pour toute variable
19
auto m = std::map<int, int>();
for (const auto& key_value : m)
{
key_value.second = 2; // compilation error
}
auto
auto utilise les règles de déduction des templates
20
#include <vector>
std::vector<int> fun()
{
// return something
}
int main()
{
const auto container = fun();
for (const auto& value : container)
{
// do something
}
}
auto
facilite la maintenance de code
21
#include <deque>
std::deque<int> fun()
{
// return something
}
int main()
{
const auto container = fun();
for (const auto& value : container)
{
// do something
}
}
facilite la maintenance de code
auto
21
#include <deque>
std::deque<int> fun()
{
// return something
}
int main()
{
const auto container = fun();
for (const auto& value : container)
{
// do something
}
}
fun() change
facilite la maintenance de code
auto
21
#include <deque>
std::deque<int> fun()
{
// return something
}
int main()
{
const auto container = fun();
for (const auto& value : container)
{
// do something
}
}
main() ne change pas!
fun() change
facilite la maintenance de code
auto
22
• permet d’adopter un style de programmation fonctionnelle
‣ Scala : val x = fun()
‣ C++ : const auto x = fun();
• auto utilise les règles de déduction des templates
‣ sauf pour le cas des listes d’initialisation (initializer_list)
auto
23
const auto x = foo();
void* _ = x;
error: cannot initialize a variable of type 'void *' with an lvalue of type
'TYPE'
  void* _ = x;
        ^   ~
1 error generated.
auto
connaître le type déduit (tout compilateur)
Initialisation uniformisée
24
struct foo {};
struct bar
{
bar(foo){}
};
int main()
{
// function declaration or variable definition?
bar b(foo());
}
“most vexing parse”
• define variable b of type bar with an instance of foo; or
• declare function b which returns a bar and takes a function with no parameter and which
returns a foo
25
Initialisation uniformisée
struct foo {};
struct bar
{
bar(foo){}
};
int main()
{
// add parentheses to declare a variable
bar b((foo()));
}
“most vexing parse”
26
struct foo {};
struct bar
{
bar(foo){}
};
int main()
{
// brace initializer
bar b{foo{}};
// better
auto b1 = bar{foo{}};
}
solution : utiliser la syntaxe pour initialiser les agrégats : {}
Initialisation uniformisée
27
struct foo
{
int i_;
foo(int i) : i_(i) {}
};
struct bar
{
int i_;
};
int main()
{
const auto f = foo{42};
const auto b = bar{42};
}
pour construire un objet
Initialisation uniformisée
28
class foo
{
int i_;
public:
foo(int i) : i_(i) {}
};
struct bar
{
int i_;
};
int main()
{
const auto f = foo{42};
const auto b = bar{42};
}
pour construire struct ou class
Initialisation uniformisée
29
struct foo
{
int i;
};
foo fun()
{
return {42};
}
int main()
{
const auto f = fun();
}
concision : on peut omettre le type de retour
Initialisation uniformisée
29
struct foo
{
int i;
};
foo fun()
{
return {42};
}
int main()
{
const auto f = fun();
}
concision : on peut omettre le type de retour
pas de type explicite
Initialisation uniformisée
30
#include <map>
int main()
{
auto m = std::map<int,int>{};
// old style
m.insert(std::make_pair(1, 2));
// new style
m.insert({1, 2});
}
exemple : insertion dans un conteneur associatif
Initialisation uniformisée
31
double a, b, c;
int x{a + b + c};
int y(a + b + c);
int z = a + b + c;
ne compile pas :
perte de précision
OK (mais mal)
idem
plus de sécurité
Initialisation uniformisée
32
• Que choisir?
• “{}”
‣ résistant au most vexing parse
‣ empêche la perte de précision
‣ utilisation plus cohérente
• “()”
‣ ne casse pas les habitudes
‣ pas de confusion avec initializer_list (à suivre…)
Initialisation uniformisée
initializer_list
33
#include <iostream>
#include <vector>
int main()
{
const auto vec = std::vector<int>{1,2,3};
for (const auto& x : vec)
{
std::cout << x;
}
}
même syntaxe que pour initialiser les agrégats
initializer_list
33
#include <iostream>
#include <vector>
int main()
{
const auto vec = std::vector<int>{1,2,3};
for (const auto& x : vec)
{
std::cout << x;
}
}
déclaration “naturelle”
même syntaxe que pour initialiser les agrégats
initializer_list
34
• C++11 définit un nouveau type
‣ std::initializer_list<T>
‣ #include <initializer_list>
• Utilise la même syntaxe d'initialisation des tableaux C
• {1,2,3}
• Le contenu de la liste est copié
initializer_list
35
#include <initializer_list>
int main()
{
// deduced to std::initializer_list<int>
const auto list = {1,2,2};
for (auto x : list)
{
// ...
}
}
exception : auto + {} initializer_list
auto utilise la déduction des templates
initializer_list
35
#include <initializer_list>
int main()
{
// deduced to std::initializer_list<int>
const auto list = {1,2,2};
for (auto x : list)
{
// ...
}
}
ne pas oublier!
exception : auto + {} initializer_list
auto utilise la déduction des templates
initializer_list
36
void foo(std::initializer_list<int> list)
{
for (auto i : list) {}
}
foo({1,2,3});
initializer_list en paramètre
initializer_list
36
void foo(std::initializer_list<int> list)
{
for (auto i : list) {}
}
foo({1,2,3});
passage par valeur
initializer_list en paramètre
initializer_list
37
struct bar
{
bar(std::initializer_list<int> list);
};
const auto b = bar{1,2,3};
can be used to construct objects
initializer_list
38
void foo(int i, std::initializer_list<int> li)
{
std::cout << i << " : ";
for (auto x : li) std::cout << x << ",";
}
foo(42, {3,2,3,4});
utilisable avec d’autres arguments
initializer_list
39
for (auto x : {1,2,3,4})
{
std::cout << x << ',';
}
utilisable dans une boucle simplifiée
initializer_list
40
#include <iostream>
#include <initializer_list>
struct bar
{
bar(std::initializer_list<int> list){std::cout << "listn";}
bar(int) {std::cout << "intn";}
};
int main()
{
const auto b = bar{1};
}
qu’affiche le code suivant?
initializer_list
41
#include <iostream>
#include <vector>
int main ()
{
const auto v1 = std::vector<int>{2};
std::cout << v1.size();
const auto v2 = std::vector<int>(2);
std::cout << v2.size();
}
mais… qu’affiche le code suivant?
initializer_list
42
• Permet de
‣ faciliter l’écriture des tests unitaires
‣ offrir une API élégante au code client
‣ …
const auto v1 = std::vector<int>{1,2,3};
auto v2 = std::vector<int>{};
v2.reserve(3);
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
vs
initializer_list
42
• Permet de
‣ faciliter l’écriture des tests unitaires
‣ offrir une API élégante au code client
‣ …
const auto v1 = std::vector<int>{1,2,3};
auto v2 = std::vector<int>{};
v2.reserve(3);
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
vs
bonus : plus d’immutabilité
User-defined literals
43
unsigned long x0 = 0ul;
long x1 = 0l;
// etc.
C++03 : il est possible de suffixer un littéral
pour y appliquer des caractéristiques
User-defined literals
44
long double
operator"" _deg (long double x)
{
return x * 3.141592/180;
}
const auto x = 90.0_deg;
pour convertir une valeur
User-defined literals
44
long double
operator"" _deg (long double x)
{
return x * 3.141592/180;
}
const auto x = 90.0_deg;
ne pas oublier “_”
pour convertir une valeur
User-defined literals
45
struct foo
{
const unsigned long long x_;
foo(unsigned long long x)
: x_(x)
{}
};
foo
operator"" _foo (unsigned long long n)
{
return {n};
}
const auto f = 123_foo;
pour construire un objet
User-defined literals
46
void
operator"" _print(const char* str, std::size_t)
{
std::cout << str;
}
"C++11 rocks!"_print;
à partir de chaînes de caractères
User-defined literals
47
struct point
{
//???
};
// Yaye!
const auto p = 3_x + 5_y;
comment obtenir cette construction?
User-defined literals
48
struct x_type{unsigned long long value;};
struct y_type{unsigned long long value;};
x_type operator"" _x(unsigned long long value) {return {value};}
y_type operator"" _y(unsigned long long value) {return {value};}
struct point
{
unsigned long long x_, y_;
private:
point(const x_type& x, const y_type& y)
: x_(x.value), y_(y.value)
{}
friend point operator+(const x_type&, const y_type&);
};
point operator+(const x_type& x, const y_type& y)
{return {x, y};};
// later...
const auto p = 3_x + 5_y;
String literals
49
const auto utf8 = u8"UTF-8 string. u2705n”;
std::cout << utf8;
const auto utf16 = u8"UTF-16 string.n”;
std::wcout << utf16;
UTF-8 string. ✅
UTF-16 string.
Gestion UTF-8, UTF-16 et UTF-32
Binary literals
50
const auto b = 0b00101010;
std::cout << b; // 42
C++14 uniquement
Séparateur ‘
51
const auto big_value = 64'000'000'000'000'000;
facilite la lecture des grandes constantes
const auto b = 0b0010'1010;
Raw string literals
52
const auto raw = R"(@
tLook Ma, no tabs!
some
text
)";
const auto escaped = "@n"
"somen"
"textn";
int
main()
{
std::cout << raw;
std::cout << "--------------n";
std::cout << escaped;
}
@
tLook Ma, no tabs!
some
text
--------------
@
some
text
enum class
53
int main()
{
enum e{pink, white, orange, brown};
const auto white = 0ul;
}
error: redefinition of 'white' as different kind of symbol
  const auto white = 0ul;
             ^
note: previous definition is here
  enum e{pink, white, orange, brown};
               ^
enum C++03 : pollution de l’espace de nommage
enum class
53
int main()
{
enum e{pink, white, orange, brown};
const auto white = 0ul;
}
ne compile pas
error: redefinition of 'white' as different kind of symbol
  const auto white = 0ul;
             ^
note: previous definition is here
  enum e{pink, white, orange, brown};
               ^
enum C++03 : pollution de l’espace de nommage
enum class
54
enum e {pink, white, orange, brown};
if (pink < 99.3)
{
// ...
}
enum C++03 : conversion implicite dangereuse
enum class
54
enum e {pink, white, orange, brown};
if (pink < 99.3)
{
// ...
}
enum C++03 : conversion implicite dangereuse
compile, mais est-ce
vraiment une bonne chose?
enum class
55
int main()
{
enum class e{pink, white, orange, brown};
const auto white = 0ul;
const auto e0 = e::white;
}
enum class créé une nouvelle portée
enum class
55
int main()
{
enum class e{pink, white, orange, brown};
const auto white = 0ul;
const auto e0 = e::white;
}
enum class créé une nouvelle portée
accès à la portée de e
enum class
56
enum class e{pink, white, orange, brown};
// use std::underlying_type<e>::type to find enum’s type
type par défaut sous-jacent : int
enum class
57
enum class e : unsigned long
{pink = 0, white, orange, brown = 0xFFFFFFFFFFFFFFFF};
// use std::underlying_type<e>::type to find enum’s type
changer le type sous-jacent
enum class
58
• C++03 : il n’y a pas de type par défaut pour énumération
‣ on est donc obligé de la mettre dans les headers
‣ recompilation en cascade s’il faut rajouter un champ
• C++11 : on peut faire une pré-déclaration
‣ définition dans une unité de compilation séparée
‣ pas de recompilation en cascade
nullptr
59
void f(int) {std::cout << "f(int)";}
void f(void*){std::cout << "f(void*)";}
f(0);
f(NULL);
error: call to 'f' is ambiguous
  f(NULL);
  ^
note: candidate function
void f(int)  {std::cout << "f(int)";}
     ^
note: candidate function
void f(void*){std::cout << "f(void*)";}
f(int)
f(int)
clang/gcc Visual Studio
nullptr
60
void f(int) {std::cout << "f(int)";}
void f(void*){std::cout << "f(void*)";}
f(0);
f(nullptr);
f(int)
f(void*)
moins d’ambiguité
nullptr
61
const auto x = f();
if (x == 0)
{
// ...
}
plus de clarté
const auto x = f();
if (x == nullptr)
{
// ...
}
vs
nullptr
61
const auto x = f();
if (x == 0)
{
// ...
}
plus de clarté
const auto x = f();
if (x == nullptr)
{
// ...
}
vs intention plus claire
nullptr
62
type de nullptr
void f(int) {std::cout << "f(int)";}
void f(void*) {std::cout << "f(void*)";}
void f(std::nullptr_t) {std::cout << "f(nullptr_t)";}
f(nullptr);
alignas
63
alignas(16) int a;
alignas(32768) int b;
std::cout << &a;
std::cout << &b;
0x7fff58d2ffe0
0x7fff58d28000
contrôler l’alignement mémoire
alignof
64
std::cout << alignof(int);
std::cout << alignof(long);
std::cout << alignof(char);
std::cout << alignof(char*);
connaître l’alignement mémoire (d’un type)
4
8
1
8
Classes
• Délégation de constructeurs
• Héritage des constructeurs des classes de base
• Initialisation directe des attributs
• Contrôle de l’héritage
• Contrôle explicite des méthodes supprimées
65
Classes
66
délégation de constructeurs (1)
struct foo
{
const int x_;
foo(int x)
: x_{x}
{}
foo()
: foo{42}
{}
};
Classes
66
délégation de constructeurs (1)
struct foo
{
const int x_;
foo(int x)
: x_{x}
{}
foo()
: foo{42}
{}
};
délégation
Classes
67
délégation de constructeurs (2)
struct foo
{
const int x_;
foo(int x = 42)
: x_{x}
{}
};
on pourrait utiliser les arguments par défaut
problème : dans l’interface de la classe
Classes
68
héritage des constructeurs (1) : C++03
struct base
{
const int x_;
base(int x)
: x_(x)
{}
};
struct foo : public base
{
foo(int x)
: base(x)
{}
};
Classes
68
héritage des constructeurs (1) : C++03
struct base
{
const int x_;
base(int x)
: x_(x)
{}
};
struct foo : public base
{
foo(int x)
: base(x)
{}
};
appel explicite
Classes
69
héritage des constructeurs (2) : C++11
struct base
{
const int x_;
base(int x)
: x_(x)
{}
};
struct foo : public base
{
using base::base;
};
Classes
69
héritage des constructeurs (2) : C++11
struct base
{
const int x_;
base(int x)
: x_(x)
{}
};
struct foo : public base
{
using base::base;
};
hérite de tous les constructeurs
de base
Classes
70
initialisation directe des attributs
struct foo
{
const int x_ = 33;
foo(int x)
: x_(x)
{}
foo()
{}
};
Classes
70
initialisation directe des attributs
struct foo
{
const int x_ = 33;
foo(int x)
: x_(x)
{}
foo()
{}
};
valeur par défaut utilisée par tous
les constructeurs
Classes
71
contrôle de l’héritage avec final
struct foo final {};
struct bar : foo {};
Classes
71
contrôle de l’héritage avec final
struct foo final {};
struct bar : foo {};
ne compile pas
error: base 'foo' is marked 'final'
struct bar : foo {};
             ^
Classes
72
contrôle de l’héritage avec override (1)
struct base
{
virtual void operator()(float x) const
{std::cout << "base " << x;}
};
struct derived : public base
{
virtual void operator()(int x) const
{std::cout << "derived " << x;}
};
const auto d = derived{};
auto x = 33.3;
d(x);
Classes
72
contrôle de l’héritage avec override (1)
struct base
{
virtual void operator()(float x) const
{std::cout << "base " << x;}
};
struct derived : public base
{
virtual void operator()(int x) const
{std::cout << "derived " << x;}
};
const auto d = derived{};
auto x = 33.3;
d(x);
affichage?
Classes
73
contrôle de l’héritage avec override (2)
struct base
{
virtual void operator()(float x) const;
};
struct derived : public base
{
virtual void operator()(int x) const override;
};
Classes
73
contrôle de l’héritage avec override (2)
struct base
{
virtual void operator()(float x) const;
};
struct derived : public base
{
virtual void operator()(int x) const override;
};
error: 'operator()' marked 'override' but does not override any member functions
  virtual void operator()(int x) const
               ^
Classes
74
contrôle des fonctions membres avec default / delete (1)
struct non_copyable
{
non_copyable(){}
private:
non_copyable(const non_copyable&);
non_copyable& operator=(const non_copyable&);
};
int main()
{
const auto nc = non_copyable{};
}
error: calling a private constructor of class 'non_copyable'
  const auto nc = non_copyable{};
                  ^
Classes
75
struct non_copyable
{
non_copyable() = default;
non_copyable(const non_copyable&) = delete;
non_copyable& operator=(const non_copyable&) = delete;
};
int main()
{
const auto nc = non_copyable{};
}
error: call to deleted constructor of 'const non_copyable'
  const auto nc = non_copyable{};
             ^    ~~~~~~~~~~~~~~
note: 'non_copyable' has been explicitly marked deleted here
  non_copyable(const non_copyable&) = delete;
contrôle des fonctions membres avec default / delete (2)
Classes
76
struct base
{
virtual void operator()(int x) const;
};
struct derived : public base
{
virtual void operator()(int x) const override;
};
int main()
{
const auto override = 0ul;
const auto final = 0ul;
}
contrôle des fonctions membres avec default / delete (3)
Classes
76
struct base
{
virtual void operator()(int x) const;
};
struct derived : public base
{
virtual void operator()(int x) const override;
};
int main()
{
const auto override = 0ul;
const auto final = 0ul;
}
pas des mots-clefs
contrôle des fonctions membres avec default / delete (3)
delete
77
void foo(int){};
void foo(long) = delete;
void foo(double) = delete;
foo(42); // OK
foo(42l); // ERROR
foo(42.0); // ERROR
permet aussi d’empêcher certaines surcharges
error: call to deleted function 'foo'
  foo(42l);
  ^~~
note: candidate function has been explicitly deleted
void foo(long) = delete;
     ^
note: candidate function
void foo(int){};
     ^
note: candidate function has been explicitly deleted
void foo(double) = delete;
     ^
Rvalues references
• Ajout majeur au C++
• Travailler explicitement avec les temporaires
• Éviter des copies inutiles
‣ move semantics
• Perfect forwarding
‣ construire en place
‣ généricité améliorée
‣ …
78
Rvalues references
• lvalues
‣ un objet qui occupe une place identifiable en mémoire
- qui a une adresse
• rvalues
‣ un objet qui n’occupe pas une place identifiable en mémoire
- qui n’a pas d’adresse : temporaire
79
lvalue = rvalue
lvalue = lvalue
rvalue = lvalue
int x = 42
int x = y
42 = ...
Rvalues references
80
int foo() {return 2;}
int main()
{
foo() = 2;
}
error: lvalue required as left operand of assignment
foo() = 2;
^
Rvalues references
81
struct foo {
foo() {
std::cout << “foo()n”;
}
foo(const foo&) {
std::cout << "foo(const foo&)n”;
}
foo& operator=(const foo&) {
std::cout << "operator=(const foo&)n”;
return *this;
}
};
foo bar() {return {};}
const auto f0 = foo{};
auto f1 = bar();
f1 = bar();
interlude : copy elision
Rvalues references
81
struct foo {
foo() {
std::cout << “foo()n”;
}
foo(const foo&) {
std::cout << "foo(const foo&)n”;
}
foo& operator=(const foo&) {
std::cout << "operator=(const foo&)n”;
return *this;
}
};
foo bar() {return {};}
const auto f0 = foo{};
auto f1 = bar();
f1 = bar();
interlude : copy elision
?
Rvalues references
82
comment éviter la copie? (1)
foo* bar() {return new foo{};}
auto f1 = bar();
// later...
f1 = bar();
• Retour par pointeur
‣ Coût de l’allocation
‣ Qui est responsable du pointeur?
Rvalues references
83
comment éviter la copie? (2)
void bar(foo*);
auto f = foo{};
bar(&f);
• Passage par pointeur ou référence en paramètre
• Quid des factories?
Rvalues references
84
comment modifier un temporaire?
void bar(foo&);
//...
auto f = foo{};
bar(f1);
bar(foo{});
utile pour un objet fonction avec état (cache, etc.)
Rvalues references
84
comment modifier un temporaire?
void bar(foo&);
//...
auto f = foo{};
bar(f1);
bar(foo{}); ne compile pas
utile pour un objet fonction avec état (cache, etc.)
Rvalues references
85
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
bar(foo{42});
Rvalues references
85
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
bar(foo{42});
foo&& : rvalue reference
Rvalues references
85
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
bar(foo{42});
foo&& : rvalue reference
foo{42} : rvalue expression
Rvalues references
85
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
bar(foo{42});
foo&& : rvalue reference
foo{42} : rvalue expression
f : lvalue expression
on peut prendre l’adresse de f
Rvalues references
85
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
bar(foo{42});
foo&& : rvalue reference
foo{42} : rvalue expression
f : lvalue expression
on peut prendre l’adresse de f
le passage en paramètre “nomme” le temporaire
celui-ci devient une lvalue : on peut prendre son adresse sur la pile
Rvalues references
86
une rvalue reference n’accepte que des rvalue expressions
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
auto f = foo{42};
bar(f);
Rvalues references
86
une rvalue reference n’accepte que des rvalue expressions
struct foo{int x;};
void bar(foo&& f)
{
std::cout << f.x << 'n';
}
//...
auto f = foo{42};
bar(f);
erreur : f n’est pas une rvalue expression
Rvalues references
87
ref-qualifiers
struct foo
{
void operator()() const
&&
{
std::cout << "&&n";
}
void operator()() const
&
{
std::cout << "&n";
}
};
//...
const auto f = foo{};
f();
foo{}();
Rvalues references
87
ref-qualifiers
struct foo
{
void operator()() const
&&
{
std::cout << "&&n";
}
void operator()() const
&
{
std::cout << "&n";
}
};
//...
const auto f = foo{};
f();
foo{}();
en tant que
rvalue
en tant que
lvalue
Move semantics
• Permet d’exprimer le “déplacement” d’une ressource
• Permet d’exprimer le changement de propriétaire d’une entité
non copiable
‣ mutex, fichier, …
• Utilisation des rvalues references
• Utilisation de std::move()
‣ instruction au compilateur
‣ ne génère aucun code
88
Ressource2Ressource1
Move semantics
89
A B
X Ressource1
Move semantics
89
A B
Move semantics
90
struct foo {
some_type* ressource;
foo& operator=(const foo& rhs) {
// destroy this->ressource
// clone rhs.ressource
// attach this cloned ressource to this->ressource
}
};
auto f1 = foo{};
auto f2 = foo{};
// later...
f2 = f1;
copie
Move semantics
90
struct foo {
some_type* ressource;
foo& operator=(const foo& rhs) {
// destroy this->ressource
// clone rhs.ressource
// attach this cloned ressource to this->ressource
}
};
auto f1 = foo{};
auto f2 = foo{};
// later...
f2 = f1;
copie
O(n)
Move semantics
91
auto f1 = foo{};
//...
auto f2 = std::move(f1);
// now f1 is “invalid”
transfert de ressource (1)
move() transforme une lvalue expression en rvalue expression
Move semantics
92
struct foo {
some_type* ressource;
foo& operator=(foo&& rhs) {
// destroy this->ressource
// copy rhs.ressource pointer to this->ressource
}
};
auto f1 = foo{};
auto f2 = foo{};
//...
f2 = std::move(f1);
transfert de ressource (2)
Move semantics
92
struct foo {
some_type* ressource;
foo& operator=(foo&& rhs) {
// destroy this->ressource
// copy rhs.ressource pointer to this->ressource
}
};
auto f1 = foo{};
auto f2 = foo{};
//...
f2 = std::move(f1);
transfert de ressource (2)
O(1)
Move semantics
92
struct foo {
some_type* ressource;
foo& operator=(foo&& rhs) {
// destroy this->ressource
// copy rhs.ressource pointer to this->ressource
}
};
auto f1 = foo{};
auto f2 = foo{};
//...
f2 = std::move(f1);
transfert de ressource (2)
O(1) mais pas toujours…
Move semantics
93
struct foo
{
foo() = default;
foo(const foo&) = default;
foo(foo&&) = delete;
};
int main()
{
const auto f = foo{};
}
Move semantics
93
struct foo
{
foo() = default;
foo(const foo&) = default;
foo(foo&&) = delete;
};
int main()
{
const auto f = foo{};
}
ne compile pas
Move semantics
94
struct foo {};
foo return_by_value()
{
const auto f = foo{};
return f;
}
void take_by_value(foo) {}
auto f1 = foo{};
auto f2 = f1;
auto f3 = std::move(f1);
take_by_value(f2);
take_by_value(std::move(f2));
auto f4 = return_by_value();
f1 = return_by_value();
quelles sont
les opérations
appelées?
Move semantics
95
transfert de propriété
struct foo
{
some_type ressource;
// forbid copy
foo(const foo&) = delete;
foo& operator=(const foo&) = delete;
foo(foo&& rhs) noexcept
{
// 1. acquire ownership of rhs.ressource
// 2. free rhs from its ownership
}
foo& operator=(foo&&) noexcept
{
// 1. release ownership of current ressource
// 2. acquire ownership of rhs.ressource
// 3. free rhs from its ownership
}
};
Move semantics
96
struct foo;
foo bar()
{
auto f = foo{};
// later...
return std::move(f);
}
std::move en retour d’une fonction
Move semantics
96
struct foo;
foo bar()
{
auto f = foo{};
// later...
return std::move(f);
}
mauvaise idée
std::move en retour d’une fonction
Move semantics
97
const rvalue references?
void bar(const foo&& f); // what for if we can't move f?
void bar(const foo&); // read only
réellement utile pour interdire tout usage des rvalues
struct foo
{
foo() {};
foo(foo&&) = delete;
foo(const foo&&) = delete;
};
// later...
const foo f0;
foo f1(std::move(f0));
void bar(const foo&& f); // what for if we can't move f?
void bar(const foo&); // read only
Move semantics
98
struct foo;
foo f1;
foo f2;
std::swap(f1, f2);
C++03 vs C++11
foo()
foo()
foo(foo&&)
operator=(foo&&)
operator=(foo&&)
foo()
foo()
foo(const foo&)
operator=(const foo&)
operator=(const foo&)
C++03 C++11
decltype
99
permet de connaître le type d’une expression
int* foo();
using ty0 = decltype(foo);
print_type<ty0>();
using ty1 = decltype(foo());
print_type<ty1>();
void print_type() [T = int *()]
void print_type() [T = int *]
trailing return type
100
auto foo() -> int
{
return 42;
}
std::cout << foo();
type de retour après les paramètres
trailing return type
101
template <typename T1, typename T2>
???
add(const T1& x, const T2& y)
{
return x + y;
}
si le type de retour dépend des paramètres
template <typename T1, typename T2>
auto
add(const T1& x, const T2& y)
-> decltype(x+y)
{
return x + y;
}
trailing return type
102
double (*get_fun2(bool arg))(double)
{
if (arg) return std::cos;
else return std::sin;
}
plus de lisibilité
auto get_fun(bool arg)
-> double(*)(double)
{
if (arg) return std::cos;
else return std::sin;
}
vs
trailing return type
103
C++14 : on peut se passer du type de retour
template <typename T1, typename T2>
auto
add(const T1& x, const T2& y)
-> decltype(x+y)
{
return x + y;
}
trailing return type
104
C++14 : on peut se passer du type de retour
mais pas toujours…
auto get_fun(bool arg)
{
if (arg) return std::cos;
else return std::sin;
}
error: cannot deduce return type 'auto' from returned value of type '<overloaded
function type>'
  if (arg) return std::cos;
105
lambdas
struct print1
{
void operator()(int x) const
{
std::cout << x << 'n';
}
};
void print2(int x)
{
std::cout << x << 'n';
}
const auto v = {1,2,3};
std::for_each(begin(v), end(v), print1{});
std::for_each(begin(v), end(v), print2);
programmation fonctionnelle :
objets fonctions et fonctions en argument
lambdas
106
const auto v = {1,2,3};
std::for_each( begin(v), end(v)
, [](int x){std::cout << x << ‘n';}
);
const auto v = {1,2,3};
const auto print =
[](int x){std::cout << x << 'n';};
std::for_each(begin(v), end(v), print);
par l’exemple
const auto fun = []{return 42;};
const auto x = fun();
std::cout << x << 'n';
lambdas
107
const auto print = [](int x){std::cout << x;};
print_type<decltype(print)>();
void print_type() [with T = const main()::<lambda(int)>]
type d’un lambda
il n’y pas de type pour les lambdas en tant que tel
auto est quasi-obligatoire si on veut stocker un lambda
ou presque…
lambdas
108
anatomie d’un lambda
[capture](params) -> ret_type {body}
comment “capturer”
l’environnement corps de la fonction
peut-être omis
type de retour si nécessaire
lambdas
109
spécification du type de retour (1)
struct foo {};
struct bar : public foo {};
struct baz : public foo {};
const auto foo_maker = [](bool value)
{
if (value) return new bar{};
else return new baz{};
};
const auto ptr = foo_maker(true);
error: return type 'baz *' must match previous return type 'bar *' when lambda
expression has unspecified explicit return type
                             else       return new baz{};
                                        ^
lambdas
110
spécification du type de retour (2)
struct foo {};
struct bar : public foo {};
struct baz : public foo {};
const auto foo_maker = [](bool value) -> foo*
{
if (value) return new bar{};
else return new baz{};
};
const auto ptr = foo_maker(true);
lambdas
110
spécification du type de retour (2)
struct foo {};
struct bar : public foo {};
struct baz : public foo {};
const auto foo_maker = [](bool value) -> foo*
{
if (value) return new bar{};
else return new baz{};
};
const auto ptr = foo_maker(true);
type de retour
lambdas
111
capture de l’environnement (1)
[] ne capture rien
[&] tout par référence
[=] tout par valeur
[this] capture this
[=,&b]
copie tout, mais
reférence b
[&a, b] référence a, copie b
environnement = toutes les variables de la portée englobante
lambdas
111
capture de l’environnement (1)
[] ne capture rien
[&] tout par référence
[=] tout par valeur
[this] capture this
[=,&b]
copie tout, mais
reférence b
[&a, b] référence a, copie b
environnement en
“lecture seule”
environnement = toutes les variables de la portée englobante
const auto x = 42u;
[]{std::cout << x;}(); // OK
auto x = 40u;
[]{x += 2;}(); // ERROR
struct foo
{
int x_;
int bar(int y)
{
return [this](int y)
{return x_ + y;}
(y);
}
};
std::cout << foo{40}.bar(2);
lambdas
112
auto x = 40u;
[&x](int y){x += y;}(2);
std::cout << x;
auto a = 40u;
[&](int b){a += b;}(2);
std::cout << x;
capture de l’environnement (2)
struct foo
{
int x_;
int bar(int y)
{
return [this](int y)
{return x_ + y;}
(y);
}
};
std::cout << foo{40}.bar(2);
lambdas
112
auto x = 40u;
[&x](int y){x += y;}(2);
std::cout << x;
auto a = 40u;
[&](int b){a += b;}(2);
std::cout << x;
capture de l’environnement (2)
auto x = 44u;
[=]{x -= 2;}();
lambdas
113
error: cannot assign to a variable captured by copy in a non-mutable lambda
  [=]{x -= 2;}();
      ~ ^
auto x = 44u;
[=]() mutable {x -= 2;}();
capture de l’environnement (3)
auto x = 44u;
[=]{x -= 2;}();
lambdas
113
error: cannot assign to a variable captured by copy in a non-mutable lambda
  [=]{x -= 2;}();
      ~ ^
auto x = 44u;
[=]() mutable {x -= 2;}();
obligatoire
capture de l’environnement (3)
lambdas
114
“fermeture” équivalente à un objet fonction (1)
struct closure
{
double& capture_;
int operator()(int x, int& y) const
{
return (x + y) * capture_;
}
closure() = delete;
closure& operator=(const closure&) = delete;
closure(const closure& ) = default;
closure(closure&& ) = default;
~closure() = default;
};
auto fun0 = [&d](int x, int& y){return (x + y) * d;};
lambdas
114
“fermeture” équivalente à un objet fonction (1)
struct closure
{
double& capture_;
int operator()(int x, int& y) const
{
return (x + y) * capture_;
}
closure() = delete;
closure& operator=(const closure&) = delete;
closure(const closure& ) = default;
closure(closure&& ) = default;
~closure() = default;
};
auto fun0 = [&d](int x, int& y){return (x + y) * d;};
d’où le mutable
lambdas
115
auto d = 0.0;
auto fun1 = fun0; // OK
fun1 = fun0; // KO no copy operator
using ty = decltype(fun0);
auto fun2 = ty{}; // KO no default ctor
auto fun0 = [&d](int x, int& y){return (x + y) * d;};
“fermeture” équivalente à un objet fonction (2)
lambdas
116
en paramètre d’un template
template <typename F, typename T>
void apply(T x)
{
F{}(x);
}
auto fun = [](int x){x += 1;};
apply<decltype(fun)>(0);
lambdas
117
attention aux références invalides
auto mk_fun()
-> std::function<void (int&)>
{
auto i = 33u;
return [&](int& j){j += i;};
}
int main()
{
auto j = 2;
const auto fun = mk_fun();
fun(j); // Doomed to failure
}
lambdas
117
attention aux références invalides
auto mk_fun()
-> std::function<void (int&)>
{
auto i = 33u;
return [&](int& j){j += i;};
}
int main()
{
auto j = 2;
const auto fun = mk_fun();
fun(j); // Doomed to failure
}
portée locale à mk_fun
lambdas
117
attention aux références invalides
auto mk_fun()
-> std::function<void (int&)>
{
auto i = 33u;
return [&](int& j){j += i;};
}
int main()
{
auto j = 2;
const auto fun = mk_fun();
fun(j); // Doomed to failure
}
portée locale à mk_fun
lecture de i locale à
mk_fun
lambdas
118
C++14 : paramètres génériques
const auto add
= [](const auto& x, const auto& y){return x + y;};
add(1,2);
add(std::string{"1"}, std::string{"2"});
lambdas
119
au service de l’immutabilité
const auto read_only = [&]
{
std::vector<int> res;
// super-complex initialisation
return res
}();
read_only.push_back(3); // nope, won’t do
auto read_only = std::vector<int>{};
// super-complex initialisation
// ...later
// damned, still mutable!
read_only.push_back(3);
noexcept
• C++03
‣ il faut lister dans la signature d’une fonction les exceptions qu’elle
peut lever (throw (…))
‣ difficile à maintenir
‣ pas ou peu d’aide des compilateurs
• C++11
‣ seule alternative : une fonction peut ou ne peut pas lever
d’exception
‣ avantage majeur : le compilateur a plus d’opportunités
d’optimisations
120
noexcept
121
void foo()
noexcept
{
// shall not throw any exception
}
struct foo
{
void
operator()() const
noexcept
{
// shall not throw any exception
}
};
sur fonctions libres ou membres
noexcept
122
opportunités d’optimisation
auto vec = std::vector<foo>{};
const auto f = foo{};
vec.push_back(f);
• Si vec doit agrandir son stockage interne
‣ C++03 : recopie
- si une exception est lancée pendant la copie, vec original n’est pas
modifié (strong guarantee)
‣ C++11 : strong guarantee à tenir
- move si foo(foo&&) noexcept
- copie sinon
noexcept
123
noexcept conditionnel (1)
void bar()
noexcept
{
// shall not throw any exception
}
void foo()
noexcept(noexcept(bar()))
{
bar();
}
noexcept
124
noexcept conditionnel (2)
struct bar
{
void operator()(int) noexcept {}
};
struct baz
{
void operator()(int){}
};
template <typename Fun>
void foo(int x)
noexcept(noexcept(Fun{}(x)))
{
Fun{}(x);
}
foo<bar>(33);
foo<baz>(42);
noexcept
125
noexcept appliqué aux lambdas
const auto lambda
= [](int x) noexcept {return x + 40;};
std::cout << lambda(2);
• Destructeurs, operator delete et operator delete[] : noexcept par
défaut
• C’est une affirmation forte
‣ déclarer une fonction noexcept, puis plus tard enlever cette spécification
peut casser le code client
• Important pour
‣ foo(foo&&)
‣ foo& operator=(foo&&)
‣ swap(foo&, foo&)
‣ fonctions de calcul (sans allocations dynamiques)?
‣ …?
126
noexcept
constexpr
127
• const : spécifier des interfaces
- foo(const std::vector<int>&);
• constexpr : spécifier ce qui est utilisable dans des expressions
constantes (et donc possiblement évaluées à la compilation)
- tout ce qui est marqué constexpr doit pouvoir produire
un résultat constant
• Applicable aux objets et aux fonctions
Visual Studio 2013
constexpr
128
auto sz = 3;
// ERROR: sz's value not known at compilation
constexpr auto sz1 = sz;
// ERROR: sz's value not known at compilation
auto array1 = std::array<int, sz>{};
// OK: 10 is a compile-time constant
constexpr auto sz2 = 10;
// OK: sz2 is constexpr
auto array2 = std::array<int, sz2>{};
objets (1)
constexpr
129
const auto sz = 3;
// OK: sz's value is known at compilation
constexpr auto sz1 = sz;
// OK: sz's value is known at compilation
auto array1 = std::array<int, sz>{};
auto x = 3;
const auto sz = x;
// ERROR: sz's value not known at compilation
constexpr auto sz1 = sz;
// ERROR: sz's value not known at compilation
auto array1 = std::array<int, sz>{};
objets (2)
constexpr
130
fonctions
constexpr
int foo(int a)
{
return a + 1;
}
constexpr auto x = foo(42);
auto runtime_value = 0u;
std::cin >> runtime_value;
auto y = foo(runtime_value);
fonction expression constante expression non-constante
constexpr ✓ ✓
non-constexpr ✕ ✓
constexpr
131
C++11
seulement une expression autorisée
constexpr int fac(int x)
{
return x <= 1
? 1
: x * fac(x-1);
}
constexpr int fac(int x)
{
auto res = 1;
for (auto i = 1; i <= x; ++i)
{
res *= i;
}
return res;
}
C++14
plusieurs instructions autorisées
constexpr
132
classes
struct foo
{
int data;
constexpr foo(int d) : data(d) {}
constexpr int operator()() const
{return data + 1;}
constexpr operator int() const
{return data;}
};
auto f0 = foo{42};
constexpr auto f1 = foo{33};
auto i0 = f0();
constexpr auto i1 = f1();
constexpr auto i2 = static_cast<int>(f1);
auto i3 = static_cast<int>(f1);
constexpr
132
classes
struct foo
{
int data;
constexpr foo(int d) : data(d) {}
constexpr int operator()() const
{return data + 1;}
constexpr operator int() const
{return data;}
};
auto f0 = foo{42};
constexpr auto f1 = foo{33};
auto i0 = f0();
constexpr auto i1 = f1();
constexpr auto i2 = static_cast<int>(f1);
auto i3 = static_cast<int>(f1);
implicite en C++11
à mettre en C++14
constexpr
133
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
constexpr
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
en pratique (clang)…
constexpr
133
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
constexpr
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
pushq %rbp
movq %rsp, %rbp
movl $0x78, %eax
popq %rbp
retq
en pratique (clang)…
constexpr
133
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
constexpr
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
pushq %rbp
movq %rsp, %rbp
movl $0x78, %eax
popq %rbp
retq
movdqa %xmm0, %xmm1
movhlps %xmm1, %xmm1
pshufd $0x31, %xmm0, %xmm2
pmuludq %xmm1, %xmm0
pshufd $0x31, %xmm1, %xmm1
pmuludq %xmm2, %xmm1
shufps $-0x78, %xmm1, %xmm0
pshufd $-0x28, %xmm0, %xmm0
pshufd $0x1, %xmm0, %xmm1
pshufd $0x31, %xmm0, %xmm2
pmuludq %xmm0, %xmm2
pmuludq %xmm0, %xmm1
shufps $-0x78, %xmm2, %xmm1
pshufd $-0x28, %xmm1, %xmm0
movd %xmm0, %eax
cmpl %edx, %r8d
je 0x100000f5d
nopw %cs:(%rax,%rax)
imull %edi, %eax
leal -0x1(%rdi), %ecx
cmpl $0x1, %ecx
movl %ecx, %edi
ja 0x100000f50
popq %rbp
retq
nop
pushq %rbp
movq %rsp, %rbp
movl $0x78, %eax
popq %rbp
retq
en pratique (clang)…
constexpr
133
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
constexpr
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
pushq %rbp
movq %rsp, %rbp
movl $0x78, %eax
popq %rbp
retq
pushq %rbp
movq %rsp, %rbp
movl $0x1, %eax
cmpl $0x2, %edi
jb 0x100000f5d
leal -0x1(%rdi), %r8d
movl %r8d, %ecx
en pratique (clang)…
constexpr
133
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
constexpr
unsigned int f(unsigned int n)
{
return n <= 1
? 1
: n * f(n-1);
}
int main()
{
return f(5);
}
Évaluation à l’exécution Évaluation à la compilation
en pratique (clang)…
static_assert
134
#include <type_traits>
template <typename T>
constexpr bool
is_power_of_two(T x)
{
static_assert( std::is_integral<T>::value
and std::is_unsigned<T>::value
, "Integral and unsigned type expected");
return x and ((x & (x-1)) == 0);
}
int main()
{
is_power_of_two(-2);
}
faire échouer la compilation sur un message clair
error: static_assert failed "Integral and unsigned type expected"
  static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value
  ^              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static_assert
134
#include <type_traits>
template <typename T>
constexpr bool
is_power_of_two(T x)
{
static_assert( std::is_integral<T>::value
and std::is_unsigned<T>::value
, "Integral and unsigned type expected");
return x and ((x & (x-1)) == 0);
}
int main()
{
is_power_of_two(-2);
}
faire échouer la compilation sur un message clair
error: static_assert failed "Integral and unsigned type expected"
  static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value
  ^              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
expression constante requise
TEMPLATES
Généricité
136
struct foo_vector
{
foo* data;
unsigned int sz;
void push_back(const foo& x)
{
data[sz++] = x;
}
};
une nécessité pour les structures de données…
auto int_vec = int_vector{};
auto foo_vec = foo_vector{};
int_vec.push_back(1);
foo_vec.push_back(foo{});
struct int_vector
{
int* data;
unsigned int sz;
void push_back(const int& x)
{
data[sz++] = x;
}
};
Généricité
137
struct int_vector
{
int* data;
unsigned int sz;
void push_back(const int& x)
{
data[sz++] = x;
}
};
une nécessité pour les structures de données…
struct foo_vector
{
foo* data;
unsigned int sz;
void push_back(const foo& x)
{
data[sz++] = x;
}
};
Généricité
138
template <typename T>
struct vector
{
T* data;
unsigned int sz;
void push_back(const T& x)
{
data[sz++] = x;
}
};
une nécessité pour les structures de données…
auto int_vec = vector<int>{};
auto foo_vec = vector<foo>{};
int_vec.push_back(1);
foo_vec.push_back(foo{});
Généricité
138
template <typename T>
struct vector
{
T* data;
unsigned int sz;
void push_back(const T& x)
{
data[sz++] = x;
}
};
une nécessité pour les structures de données…
auto int_vec = vector<int>{};
auto foo_vec = vector<foo>{};
int_vec.push_back(1);
foo_vec.push_back(foo{});
“instanciation” du
type générique
vector
avec un entier
Généricité
139
int sum(const std::vector<int>& vec)
{
auto acc = int{};
for (const auto& x : vec)
acc = acc + x;
return acc;
}
une nécessité pour les algorithmes…
const auto vec0 = std::vector<int>{1,2,3};
const auto s0 = sum(vec0);
const auto vec1 = std::vector<foo>{1,2,3};
const auto s1 = sum(vec1);
struct foo;
foo operator+(const foo&, const foo&);
foo sum(const std::vector<foo>& vec)
{
auto acc = foo{};
for (const auto& x : vec)
acc = acc + x;
return acc;
}
Généricité
140
int sum(const std::vector<int>& vec)
{
auto acc = int{};
for (const auto& x : vec)
acc = acc + x;
return acc;
}
une nécessité pour les algorithmes…
foo sum(const std::vector<foo>& vec)
{
auto acc = foo{};
for (const auto& x : vec)
acc = acc + x;
return acc;
}
Généricité
141
template <typename T>
T sum(const std::vector<T>& vec)
{
auto acc = T{};
for (const auto& x : vec)
{
acc = acc + x;
}
return acc;
}
une nécessité pour les algorithmes…
const auto vec0 = std::vector<int>{1,2,3};
const auto s0 = sum(vec0);
const auto vec1 = std::vector<foo>{1,2,3};
const auto s1 = sum(vec1);
Généricité
141
template <typename T>
T sum(const std::vector<T>& vec)
{
auto acc = T{};
for (const auto& x : vec)
{
acc = acc + x;
}
return acc;
}
rien n’a
changé!
une nécessité pour les algorithmes…
const auto vec0 = std::vector<int>{1,2,3};
const auto s0 = sum(vec0);
const auto vec1 = std::vector<foo>{1,2,3};
const auto s1 = sum(vec1);
Structures templates
142
template <typename T>
struct foo
{
T x_;
foo(const T& x) : x_(x) {}
const T& x() const {return x_;}
T& x() {return x_;}
};
auto f_int = foo<int>{42};
f_int.x() += 2;
auto f_char = foo<char>{'a'};
f_char.x() = 'b';
Structures templates
142
template <typename T>
struct foo
{
T x_;
foo(const T& x) : x_(x) {}
const T& x() const {return x_;}
T& x() {return x_;}
};
paramètre générique : c’est
un type, pas une valeur
auto f_int = foo<int>{42};
f_int.x() += 2;
auto f_char = foo<char>{'a'};
f_char.x() = 'b';
Structures templates
142
template <typename T>
struct foo
{
T x_;
foo(const T& x) : x_(x) {}
const T& x() const {return x_;}
T& x() {return x_;}
};
paramètre générique : c’est
un type, pas une valeur
accès au type dans toute la classe
auto f_int = foo<int>{42};
f_int.x() += 2;
auto f_char = foo<char>{'a'};
f_char.x() = 'b';
Fonctions templates
143
struct foo{};
template <typename T, typename U>
void fun0(const T&, const U&);
template <typename T, typename U>
void fun1(const T&);
fun0(0, foo{});
fun1<int, foo>(0);
déduction automatique à partir des arguments
Fonctions templates
143
struct foo{};
template <typename T, typename U>
void fun0(const T&, const U&);
template <typename T, typename U>
void fun1(const T&);
fun0(0, foo{});
fun1<int, foo>(0);
déduction automatique à partir des arguments
tous les types sont
présents dans les
arguments
Fonctions templates
143
struct foo{};
template <typename T, typename U>
void fun0(const T&, const U&);
template <typename T, typename U>
void fun1(const T&);
fun0(0, foo{});
fun1<int, foo>(0);
déduction automatique à partir des arguments
tous les types sont
présents dans les
arguments
les types ne sont pas tous présents
dans les arguments, il donc les expliciter
Fonctions templates
144
template <typename T>
struct foo
{
T x;
};
template <typename T>
foo<T> mk_foo(const T& x)
{
return {x};
}
const auto f0 = foo<int>(42);
const auto f1 = mk_foo(42);
pratique pour “cacher” l’argument du template
Fonctions templates
144
template <typename T>
struct foo
{
T x;
};
template <typename T>
foo<T> mk_foo(const T& x)
{
return {x};
}
const auto f0 = foo<int>(42);
const auto f1 = mk_foo(42);
pratique pour “cacher” l’argument du template
pas de type explicite
Fonctions templates
145
les fonctions membres peuvent être aussi génériques
template <typename T>
struct foo
{
T x_;
foo(const T& x) : x_(x) {}
template <typename U>
void bar(const U&) {}
};
auto f = foo<int>{42};
f.bar(3.22);
Fonctions templates
145
les fonctions membres peuvent être aussi génériques
template <typename T>
struct foo
{
T x_;
foo(const T& x) : x_(x) {}
template <typename U>
void bar(const U&) {}
};
auto f = foo<int>{42};
f.bar(3.22);méthode générique
Templates :“fabriques”
146
• Une structure template est une “fabrique de type”
‣ template <typename T> struct foo n’est pas un type
- on ne peut pas créer d’objet de type foo<T>
‣ foo<int> est “généré” à partir de foo<T>
- c’est un type, on peut créer un objet à partir de foo<int>
• Une fonction template est une “fabrique de fonctions”
‣ on n’appelle pas directement template <typename T> foo()
Accès à un type imbriqué
147
struct bar
{
typedef int value_type;
};
void foo(const bar&)
{
bar::value_type x;
}
dans un contexte non-générique
Accès à un type imbriqué
147
struct bar
{
typedef int value_type;
};
void foo(const bar&)
{
bar::value_type x;
}
dans un contexte non-générique
OK
Accès à un type imbriqué
148
struct bar
{
typedef int value_type;
};
template <typename T>
void foo(const T&)
{
T::value_type x;
}
error: missing 'typename' prior to dependent type name 'T::value_type'
  T::value_type x;
  ^~~~~~~~~~~~~
  typename
dans un contexte générique
Accès à un type imbriqué
148
struct bar
{
typedef int value_type;
};
template <typename T>
void foo(const T&)
{
T::value_type x;
}
error: missing 'typename' prior to dependent type name 'T::value_type'
  T::value_type x;
  ^~~~~~~~~~~~~
  typename
!
dans un contexte générique
Accès à un type imbriqué
149
struct bar
{
typedef int value_type;
};
template <typename T>
void foo(const T&)
{
typename T::value_type x;
}
dans un contexte générique
Accès à un type imbriqué
149
struct bar
{
typedef int value_type;
};
template <typename T>
void foo(const T&)
{
typename T::value_type x;
}
dans un contexte générique
OK
150
Dependant templates
struct bar
{
template <typename U>
U get() const {}
};
template <typename T>
void foo(const T& b)
{
const auto x = b.get<int>();
}
auto b = bar{};
foo(b);
error: use 'template' keyword to treat 'get' as a dependent template name
  const auto x = b.get<T>();
                   ^
                   template
150
Dependant templates
struct bar
{
template <typename U>
U get() const {}
};
template <typename T>
void foo(const T& b)
{
const auto x = b.get<int>();
}
auto b = bar{};
foo(b);
error: use 'template' keyword to treat 'get' as a dependent template name
  const auto x = b.get<T>();
                   ^
                   template
!
151
Dependant templates
struct bar
{
template <typename U>
U get() const {}
};
template <typename T>
void foo(const T& b)
{
const auto x = b.template get<int>();
}
151
Dependant templates
struct bar
{
template <typename U>
U get() const {}
};
template <typename T>
void foo(const T& b)
{
const auto x = b.template get<int>();
}
OK
151
Dependant templates
struct bar
{
template <typename U>
U get() const {}
};
template <typename T>
void foo(const T& b)
{
const auto x = b.template get<int>();
}
OK
get est une fonction template qui
dépend du type T qui est lui
même un paramètre template
alias
152
// the old way
typedef int my_typedef;
// aliases
using my_alias = int;
using : successeur de typedef
alias
152
// the old way
typedef int my_typedef;
// aliases
using my_alias = int;
lecture homogène avec auto var = …;
using : successeur de typedef
alias
153
template <typename T, typename U>
struct foo{};
template <typename T>
struct templated_typedef
{
typedef foo<T, int> type;
};
int main()
{
const auto f = templated_typedef<int>::type{};
}
essayons tout de même (1)
impossible d’avoir des typedef template
alias
154
template <typename T, typename U>
struct foo{};
template <typename T>
struct templated_typedef
{
typedef foo<T, int> type;
};
template <typename T>
void bar(const typename templated_typedef<T>::type&);
int main()
{
const auto f = templated_typedef<int>::type{};
bar(f);
}
impossible d’avoir des typedef template
essayons tout de même (2)
alias
154
template <typename T, typename U>
struct foo{};
template <typename T>
struct templated_typedef
{
typedef foo<T, int> type;
};
template <typename T>
void bar(const typename templated_typedef<T>::type&);
int main()
{
const auto f = templated_typedef<int>::type{};
bar(f);
}
note: candidate template ignored: couldn't infer template argument 'T'
void bar(const typename templated_typedef<T>::type&);
impossible d’avoir des typedef template
essayons tout de même (2)
!
alias
155
template <typename T, typename U>
struct foo{};
template <typename T>
using alias_t = foo<T, int>;
int main()
{
const auto f0 = foo<double, int>{};
const auto f1 = alias_t<int>{};
}
alias templates
Règles de déductions
156
template <typename T>
void f(ParamType param);
f(expression);
deux types à déduire
template <typename T>
void f(const T& param);
int x = 42;
f(x);
pseudo-code :
Règles de déductions
156
template <typename T>
void f(ParamType param);
f(expression);
type deT
deux types à déduire
template <typename T>
void f(const T& param);
int x = 42;
f(x);
pseudo-code :
Règles de déductions
156
template <typename T>
void f(ParamType param);
f(expression);
type deT
deux types à déduire
type de param
template <typename T>
void f(const T& param);
int x = 42;
f(x);
pseudo-code :
Règles de déductions
156
template <typename T>
void f(ParamType param);
f(expression);
type deT
deux types à déduire
type de param
template <typename T>
void f(const T& param);
int x = 42;
f(x);
int
pseudo-code :
Règles de déductions
156
template <typename T>
void f(ParamType param);
f(expression);
type deT
deux types à déduire
type de param
template <typename T>
void f(const T& param);
int x = 42;
f(x);
int
const int&
pseudo-code :
Règles de déductions
• Trois cas, le type de param
‣ est un pointeur ou une référence
‣ n’est ni un pointeur ni une référence
‣ est une référence universelle
157
template <typename T>
void f(ParamType param);
Règles de déductions
158
ParamType : pointeur ou référence (1)
template <typename T>
void f(T& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int int&
f(cx) const int const int&
f(rx) const int const int&
Règles de déductions
158
ParamType : pointeur ou référence (1)
template <typename T>
void f(T& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int int&
f(cx) const int const int&
f(rx) const int const int&
idem
pour les pointeurs
Règles de déductions
159
ParamType : pointeur ou référence (2)
template <typename T>
void f(const T& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int const int&
f(cx) const int const int&
f(rx) const int const int&
Règles de déductions
159
ParamType : pointeur ou référence (2)
template <typename T>
void f(const T& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int const int&
f(cx) const int const int&
f(rx) const int const int&
idem
pour les pointeurs
Règles de déductions
160
ParamType : ni pointeur, ni référence
template <typename T>
void f(T param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int int
f(cx) int int
f(rx) int int
Règles de déductions
160
ParamType : ni pointeur, ni référence
template <typename T>
void f(T param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int int
f(cx) int int
f(rx) int int
par valeur
Règles de déductions
160
ParamType : ni pointeur, ni référence
template <typename T>
void f(T param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int int
f(cx) int int
f(rx) int int
par valeur
copie :
const est ignoré
Règles de déductions
161
ParamType : rvalue reference
template <typename T>
void f(T&& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int& int&
f(cx) const int& const int&
f(rx) const int& const int&
f(42) int int&&
lvalues
rvalues
Règles de déductions
161
ParamType : rvalue reference
template <typename T>
void f(T&& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int& int&
f(cx) const int& const int&
f(rx) const int& const int&
f(42) int int&&
idem
pour les pointeurs
lvalues
rvalues
Règles de déductions
161
ParamType : rvalue reference
template <typename T>
void f(T&& param);
int x = 2;
const int cx = x;
const int& rx = x;
T param
f(x) int& int&
f(cx) const int& const int&
f(rx) const int& const int&
f(42) int int&&
idem
pour les pointeurs
lvalues
rvalues
“référence universelle”
162
template <typename T>
void print_type()
{
std::cout << __PRETTY_FUNCTION__;
}
Afficher le type déduit
void print_type() [T = foo]
(GCC, clang)
163
template <typename T>
void print_type()
{
std::cout << __FUNCSIG__;
}
void _cdecl print_type<struct foo>(void)
(Visual Studio 2013)
Afficher le type déduit
164
template <typename T>
struct print_type;
print_type<foo>{};
(tous compilateurs)
Afficher le type déduit
error: implicit instantiation of undefined template 'print_type<foo>'
  print_type<foo>{};
  ^
164
template <typename T>
struct print_type;
print_type<foo>{};
(tous compilateurs)
Afficher le type déduit
ne pas donner de
définition
error: implicit instantiation of undefined template 'print_type<foo>'
  print_type<foo>{};
  ^
Spécialisation
165
scénario : un algorithme peut-être optimisé pour un type particulier
struct foo{};
template <typename T>
struct algo
{
void operator()()
{std::cout << "O(n)";}
};
template <>
struct algo<foo>
{
void operator()()
{std::cout << "O(1)";}
};
algo<foo>{}(); // “O(1)”
algo<int>{}(); // “O(n)”
Spécialisation
165
scénario : un algorithme peut-être optimisé pour un type particulier
struct foo{};
template <typename T>
struct algo
{
void operator()()
{std::cout << "O(n)";}
};
template <>
struct algo<foo>
{
void operator()()
{std::cout << "O(1)";}
};
algo<foo>{}(); // “O(1)”
algo<int>{}(); // “O(n)”
générique
Spécialisation
165
scénario : un algorithme peut-être optimisé pour un type particulier
struct foo{};
template <typename T>
struct algo
{
void operator()()
{std::cout << "O(n)";}
};
template <>
struct algo<foo>
{
void operator()()
{std::cout << "O(1)";}
};
algo<foo>{}(); // “O(1)”
algo<int>{}(); // “O(n)”
spécialisation
générique
Spécialisation
166
fonctions
struct foo{};
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
algo(foo{}); // “O(1)”
algo(42); // “O(n)”
Spécialisation
166
fonctions
struct foo{};
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
algo(foo{}); // “O(1)”
algo(42); // “O(n)”
générique
Spécialisation
166
fonctions
struct foo{};
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
algo(foo{}); // “O(1)”
algo(42); // “O(n)”
spécialisation
générique
Spécialisation
167
fonctions, une petite surprise…
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
void algo(const foo&)
{
std::cout << "O(1) bis";
}
algo(foo{}); // “O(1) bis”
algo(42); // “O(n)”
Spécialisation
167
fonctions, une petite surprise…
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
void algo(const foo&)
{
std::cout << "O(1) bis";
}
algo(foo{}); // “O(1) bis”
algo(42); // “O(n)”
spécialisation
générique
surcharge
Spécialisation
167
fonctions, une petite surprise…
template <typename T>
void algo(const T&)
{
std::cout << "O(n)";
}
template <>
void algo(const foo&)
{
std::cout << "O(1)";
}
void algo(const foo&)
{
std::cout << "O(1) bis";
}
algo(foo{}); // “O(1) bis”
algo(42); // “O(n)”
spécialisation
générique
surcharge
préférer les surcharges
aux spécialisations
Spécialisation partielle
168
scénario : un algorithme peut-être optimisé pour un
conteneur générique particulier
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
Spécialisation partielle
168
scénario : un algorithme peut-être optimisé pour un
conteneur générique particulier
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
générique
Spécialisation partielle
168
scénario : un algorithme peut-être optimisé pour un
conteneur générique particulier
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
générique
spécialisation
partielle
Spécialisation partielle
169
on peut garder des spécialisations totales
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
template <>
struct algo<std::vector<double>>
{
void operator()() {std::cout << "O(1) double";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
algo<std::vector<double>>{}(); // “O(1) double”
Spécialisation partielle
169
on peut garder des spécialisations totales
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
template <>
struct algo<std::vector<double>>
{
void operator()() {std::cout << "O(1) double";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
algo<std::vector<double>>{}(); // “O(1) double”
générique
Spécialisation partielle
169
on peut garder des spécialisations totales
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
template <>
struct algo<std::vector<double>>
{
void operator()() {std::cout << "O(1) double";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
algo<std::vector<double>>{}(); // “O(1) double”
générique
spécialisation
partielle
Spécialisation partielle
169
on peut garder des spécialisations totales
template <typename T>
struct algo
{
void operator()() {std::cout << "O(n)";}
};
template <typename T>
struct algo<std::vector<T>>
{
void operator()() {std::cout << "O(1)";}
};
template <>
struct algo<std::vector<double>>
{
void operator()() {std::cout << "O(1) double";}
};
algo<int>{}(); // “O(n)”
algo<std::vector<int>>{}(); // “O(1)”
algo<std::vector<double>>{}(); // “O(1) double”
générique
spécialisation
partielle
spécialisation
totale
Template-template arguments
170
template <typename T>
struct vector {};
template <typename T, template <typename DUMMY> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, vector>{};
prendre en paramètre template un template (1)
Template-template arguments
170
template <typename T>
struct vector {};
template <typename T, template <typename DUMMY> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, vector>{};
obligatoire
prendre en paramètre template un template (1)
Template-template arguments
170
template <typename T>
struct vector {};
template <typename T, template <typename DUMMY> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, vector>{};
obligatoire
nom sans importance
peut être omis
prendre en paramètre template un template (1)
Template-template arguments
170
template <typename T>
struct vector {};
template <typename T, template <typename DUMMY> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, vector>{};
obligatoire
nom sans importance
peut être omis
T est passé à Container
prendre en paramètre template un template (1)
Template-template arguments
171
template <typename T, template <typename> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, std::vector>{};
prendre en paramètre template un template (2)
error: template template argument has different template parameters than its
corresponding template template parameter
  foo<int, std::vector>{};
                ^
note: too many template parameters in template template argument
template <class _Tp, class _Allocator = allocator<_Tp> >
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
attention aux nombres de paramètres…
Template-template arguments
171
template <typename T, template <typename> class Container>
struct foo
{
Container<T> cont_;
};
foo<int, std::vector>{};
prendre en paramètre template un template (2)
!
error: template template argument has different template parameters than its
corresponding template template parameter
  foo<int, std::vector>{};
                ^
note: too many template parameters in template template argument
template <class _Tp, class _Allocator = allocator<_Tp> >
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
attention aux nombres de paramètres…
Valeurs en paramètres template
172
template <int Value>
struct foo
{
void operator()() const
{
std::cout << Value;
}
};
foo<42>{}();
on peut utiliser des valeurs comme paramètres template
template <bool Value>
struct foo
{
void operator()() const
{
std::cout << Value;
}
};
foo<true>{}();
Valeurs en paramètres template
173
expressions constantes seulement
template <int Value>
struct foo;
const auto i = 42;
auto j = i;
foo<i>{}(); // OK
foo<j>{}(); // ERROR
error: non-type template argument is not a constant expression
  foo<j>{}(); // ERROR
      ^
note: read of non-const variable 'j' is not allowed in a constant expression
Valeurs en paramètres template
173
expressions constantes seulement
template <int Value>
struct foo;
const auto i = 42;
auto j = i;
foo<i>{}(); // OK
foo<j>{}(); // ERROR
!
error: non-type template argument is not a constant expression
  foo<j>{}(); // ERROR
      ^
note: read of non-const variable 'j' is not allowed in a constant expression
Valeurs en paramètres template
174
la spécialisation fonctionne aussi
template <int Value>
struct fact
{
static const auto value = Value * fact<Value - 1>::value;
};
template <>
struct fact<0>
{
static const auto value = 1;
};
std::cout << fact<5>::value; // 120
Valeurs en paramètres template
174
la spécialisation fonctionne aussi
template <int Value>
struct fact
{
static const auto value = Value * fact<Value - 1>::value;
};
template <>
struct fact<0>
{
static const auto value = 1;
};
std::cout << fact<5>::value; // 120
récursion : cas général
récursion : cas de base
Curiously RecurringTemplate Pattern (CRTP)
175
template <typename T>
struct foo
{};
struct bar
: public foo<bar>
{};
classe héritée générique
Variadic templates
176
template<typename T>
T add(T x)
{
return x;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
add(1,2); // 3
add(1,2,3); // 6
add(1,2,3,4); // 10
// etc.
nombre variable de paramètres template
Variadic templates
176
template<typename T>
T add(T x)
{
return x;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
add(1,2); // 3
add(1,2,3); // 6
add(1,2,3,4); // 10
// etc.
nombre variable de paramètres template
“parameter pack”
Variadic templates
176
template<typename T>
T add(T x)
{
return x;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
add(1,2); // 3
add(1,2,3); // 6
add(1,2,3,4); // 10
// etc.
nombre variable de paramètres template
expansion du pack
“parameter pack”
Variadic templates
177
int add(int arg1)
{
return arg1;
}
int add(int arg1, int arg2)
{
return arg1 + add(arg2);
}
int add(int arg1, int arg2, int arg3)
{
return arg1 + add(arg2, arg3);
}
add(1,2,3); // 6
code “déroulé”
template<typename T>
T add(T x)
{
return x;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
Variadic templates
178
raisonnement programmation fonctionnelle pure
template<typename T>
T add(T x)
{
return v;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
“déconstruction" de la liste de types head::tail
Caml, Standard ML, Haskell, Scala, etc.
Variadic templates
178
raisonnement programmation fonctionnelle pure
template<typename T>
T add(T x)
{
return v;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
head
“déconstruction" de la liste de types head::tail
Caml, Standard ML, Haskell, Scala, etc.
Variadic templates
178
raisonnement programmation fonctionnelle pure
template<typename T>
T add(T x)
{
return v;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
head tail
“déconstruction" de la liste de types head::tail
Caml, Standard ML, Haskell, Scala, etc.
Variadic templates
178
raisonnement programmation fonctionnelle pure
template<typename T>
T add(T x)
{
return v;
}
template<typename T, typename... Ts>
T add(T x, Ts... xs)
{
return x + add(xs...);
}
head tail
“déconstruction" de la liste de types head::tail
Caml, Standard ML, Haskell, Scala, etc.
head
Variadic templates
179
template<typename T>
T add(T v)
{
return v;
}
template<typename T, typename... Args>
T add(T first, Args... args)
{
return first + add(args...);
}
std::string operator ""_s(const char *str, std::size_t)
{
return {str};
}
add(“1”_s,"2"_s,"3"_s); // “123”
pour le plaisir…
Variadic templates
180
// Signature of a meta-function that add integers
template <int... Xs>
struct add;
template <int X, int... Xs>
struct add<X, Xs...>
{
static const auto value = X + add<Xs...>::value;
};
template <int X>
struct add<X>
{
static const auto value = X ;
};
add<1,2,3>::value; // 6
les struct/class aussi
Variadic templates
181
connaître la taille d’un pack avec sizeof...
template <typename... Ts>
std::size_t foo()
{
return sizeof...(Ts);
}
std::cout << foo<int, double, char>(); // 3
Variadic templates
182
expansion d’un pack (1)
template <typename... Ts>
struct foo{};
template<typename X, typename Y, typename... Z>
void
bar(X, Y, Z...)
{
print_type< foo<X, Y, Z...> >();
print_type< foo<X, Z..., Y> >();
print_type< foo<Z..., X, Y> >();
}
bar(int{}, int{}, double{}, std::string{});
void print_type() [T = foo<int, int, double, std::__1::basic_string<char> >]
void print_type() [T = foo<int, double, std::__1::basic_string<char>, int>]
void print_type() [T = foo<double, std::__1::basic_string<char>, int, int>]
Variadic templates
182
expansion d’un pack (1)
template <typename... Ts>
struct foo{};
template<typename X, typename Y, typename... Z>
void
bar(X, Y, Z...)
{
print_type< foo<X, Y, Z...> >();
print_type< foo<X, Z..., Y> >();
print_type< foo<Z..., X, Y> >();
}
bar(int{}, int{}, double{}, std::string{});
void print_type() [T = foo<int, int, double, std::__1::basic_string<char> >]
void print_type() [T = foo<int, double, std::__1::basic_string<char>, int>]
void print_type() [T = foo<double, std::__1::basic_string<char>, int, int>]
Z… en dernière position
Variadic templates
183
expansion d’un pack (2)
template<typename... Values>
std::array<int, sizeof...(Values)>
make_array(Values... values)
{
return std::array<int, sizeof...(Values)>{values...};
}
const auto a = make_array(1,2,3);
template<typename... Args>
void
foo(Args... values)
{
int tab[sizeof...(Args)] = {values...};
}
foo(1,2,3);
Variadic templates
184
expansion d’un pack : héritage
struct base1
{
void foo(){}
};
struct base2
{
void bar(){}
};
template <typename... Bases>
class derived
: public Bases...
{};
auto d = derived<base1, base2>{};
d.foo();
d.bar();
Variadic templates
185
spécialisation
template <typename... Ts>
struct foo
{};
template <typename... Ts>
struct bar;
template <typename... Ts>
struct bar<foo<Ts...>>
{};
using foo_type = foo<int, char>;
using bar_type = bar<foo_type>;
Variadic templates
185
spécialisation
template <typename... Ts>
struct foo
{};
template <typename... Ts>
struct bar;
template <typename... Ts>
struct bar<foo<Ts...>>
{};
using foo_type = foo<int, char>;
using bar_type = bar<foo_type>;
permet d’accéder
à liste de
paramètres de foo
Variadic templates
186
template <typename... Ts>
struct tuple {};
template <typename T, typename... Ts>
struct tuple<T, Ts...>
: tuple<Ts...>
{
tuple(T t, Ts... ts)
: tuple<Ts...>(ts...)
, current(t)
{}
T current;
};
structure récursive
Références universelles
187
template <typename T>
void f(T&& param);
• Dénomination de Scott Meyers
• En présence d’une déduction de type (templates)
• Il faudrait parler de
‣ forwarding reference
‣ en cours de standardisation (la dénomination)
‣ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/
n4164.pdf
Références universelles
187
template <typename T>
void f(T&& param);
• Dénomination de Scott Meyers
• En présence d’une déduction de type (templates)
• Il faudrait parler de
‣ forwarding reference
‣ en cours de standardisation (la dénomination)
‣ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/
n4164.pdf
&& sur paramètre template
Perfect forwarding
188
le problème : passer des paramètres
de manière transparente à une sous-fonction
template <typename Fun, typename T>
void logger(const T& x)
{
std::cout << "beforen";
Fun{}(x);
std::cout << "aftern";
}
template <typename Fun, typename T>
void logger(T& x)
{
std::cout << "beforen";
Fun{}(x);
std::cout << "aftern";
}
et s’il y a plusieurs paramètres?…
Perfect forwarding
solution : std::forward
template <typename Fun, typename Arg>
void logger(Arg&& x)
{
std::cout << "beforen";
Fun{}(std::forward<Arg>(x));
std::cout << "aftern";
}
Perfect forwarding
190
comment ça fonctionne? (1)
A& & → A&
A& && → A&
A&& & → A&
A&& && → A&&
si invocation f(a)
a de type A Arg x
lvalue A& A&
rvalue A&& A&&
template<typename Arg>
void f(Arg&& x);
“collapsing rules”
application des collapsing rules
Perfect forwarding
191
comment ça fonctionne? (2)
template<class T>
T&&
forward(typename remove_reference<T>::type& a)
{
return static_cast<T&&>(a);
}
Perfect forwarding
192
void logger(X& && x)
{
std::cout << "beforen";
Fun{}(std::forward<X&>(x));
std::cout << "aftern";
}
X& &&
forward(typename remove_reference<X&>::type& a)
{
return static_cast<X& &&>(a);
}
void logger(X& x)
{
std::cout << "beforen";
Fun{}(std::forward<X&>(x));
std::cout << "aftern";
}
X&
forward(X& a)
{
return static_cast<X&>(a);
}
sur une lvalue
Perfect forwarding
193
sur une rvalue
void logger(X&& && x)
{
std::cout << "beforen";
Fun{}(std::forward<X&&>(x));
std::cout << "aftern";
}
X&& &&
forward(typename remove_reference<X&&>::type& a)
{
return static_cast<X&& &&>(a);
}
void logger(X&& x)
{
std::cout << "beforen";
Fun{}(std::forward<X&&>(x));
std::cout << "aftern";
}
X&&
forward(X& a)
{
return static_cast<X&&>(a);
}
Perfect forwarding vs move
194
void foo(int&&) {std::cout << "int&&";}
void foo(const int&) {std::cout << "const int&";}
void foo(int&) {std::cout << "int&";}
template <typename T>
void apply(T&& x)
{
std::cout << "A";
foo(std::forward<T>(x));}
}
void apply(int&& x)
{
std::cout << "B";
foo(std::move(x));
}
apply(i);
apply(ri);
apply(cri);
apply(42);
apply(std::move(i));
quel est l’affichage?
auto i = 2;
auto& ri = i;
const auto& cri = i;
BIBLIOTHÈQUE STANDARD
196
begin/end
auto vec = std::vector<int>{1,2,3};
auto it = begin(vec); // vec.begin()
*it = 42;
auto cit = cbegin(vec); // vec.cbegin()
*cit = 33; // error
fonctions libres
for (auto cit = cbegin(vec); cit != cend(vec); ++cit)
{
// ...
}
197
begin/end
tableaux C
int tab[] = {1,2,3};
for (auto it = std::begin(tab); it != std::end(tab); ++it)
{
*it = 0;
}
197
begin/end
tableaux C
int tab[] = {1,2,3};
for (auto it = std::begin(tab); it != std::end(tab); ++it)
{
*it = 0;
}
appel “fully qualified”
198
next/prev
auto vec = std::vector<int>{1,2,3};
auto it = begin(vec);
auto it1 = next(it); // return a copy incremented by 1
auto it2 = next(it, 2); // return a copy incremented by 2
auto it3 = prev(it2); // return a copy decremented by 1
auto it4 = prev(it3, 1); // return a copy decremented by 1
199
move
déplacer un conteneur
auto vec1 = std::vector<foo>{foo{}, foo{}};
auto vec2 = std::vector<foo>{};
std::cout << vec1.size();
std::cout << vec2.size();
std::move(begin(vec1), end(vec1), std::back_inserter(vec2));
std::cout << vec1.size();
std::cout << vec2.size();
200
all_of, any_of, any_of
auto vec1 = std::vector<bool>{true, true, true};
auto vec2 = std::vector<bool>{true, false, true};
std::cout << std::boolalpha;
std::cout << std::all_of( begin(vec1), end(vec1)
, [](bool x){return x;});
std::cout << std::any_of( begin(vec2), end(vec2)
, [](bool x){return not x;});
201
emplace, emplace_back
construction en place grâce au perfect forwarding
struct foo
{
foo(int)
{std::cout << "foo(int)";}
foo(const foo&)
{std::cout << "foo(const foo&)";}
foo& operator=(const foo&)
{std::cout << "operator=(const foo&)"; return *this;}
foo(foo&&)
{std::cout << "foo(foo&&)";}
foo& operator=(foo&&)
{std::cout << "operator=(foo&&)"; return *this;}
};
auto vec1 = std::vector<foo>{};
vec1.reserve(3);
auto f = foo{1};
vec1.push_back(f);
vec1.push_back(foo{2});
vec1.emplace_back(3);
202
shrink_to_fit
deque, string et vector
auto vec1 = std::vector<int>{};
std::cout << vec1.capacity();
vec1.push_back(1);
vec1.push_back(1);
vec1.push_back(1);
std::cout << vec1.capacity();
vec1.shrink_to_fit();
std::cout << vec1.capacity();
peut libérer de la mémoire
203
forward_list
liste simplement chaînée
auto l = std::forward_list<int>{1,2,3};
std::cout << l.size(); // error, no size() member
auto cit0 = begin(l);
auto cit1 = next(cit0);
auto cit2 = prev(cit1); // error, not bidirectional
204
array
auto a1 = std::array<int, 3>{};
const auto cit = a1.cbegin();
const auto& x = a1[2]; // use like a C-array
const auto& y = a1.at(10); // std::out_of_range
auto a2 = std::array<int, 3>{};
if (a1 == a2)
{
// ...
}
auto a3 = a1;
// etc.
tableaux C pour le C++
strictement les mêmes performances!
à privilégier dans tout nouveau code
205
array
const auto a0 = std::array<int, 3>{1,2,3};
const auto a1 = a0;
toutes les facilités du C++ (1)
const int t0[3] = {1,2,3};
int t1[3];
std::copy(t0, t0 + 3, t1);
vs
copie
205
array
const auto a0 = std::array<int, 3>{1,2,3};
const auto a1 = a0;
toutes les facilités du C++ (1)
dangereux
const int t0[3] = {1,2,3};
int t1[3];
std::copy(t0, t0 + 3, t1);
vs
copie
206
array
const auto a0 = std::array<int, 3>{1,2,3};
const auto res = std::find(begin(a0), end(a0), 3);
if (res != end(a0))
{
std::cout << "foundn";
}
toutes les facilités du C++ (2)
utilisation des algorithmes STL
207
array
auto a0 = std::array<int, 3>{1, 2, 3};
auto a1 = std::array<int, 3>{1, 99, 3};
const auto res = memcmp(a0.data(), a1.data(), 3 * sizeof(int));
std::cout << res << 'n';
memcpy(a0.data(), a1.data(), 3 * sizeof(int));
for (auto x : a0)
{
std::cout << x << 'n';
}
utilisation avec les fonctions C
.data() pour accéder au tableau C
208
tuple
conteneur de types hétérogène
auto t1 = std::tuple<int, char, foo>{2, 'a', foo{}};
auto t2 = std::make_tuple(2, 'a', foo{});
const auto& f = std::get<2>(t2);
209
tuple
permet de retourner plusieurs valeurs
sans avoir à créer un nouveau type (1)
struct foo {};
std::tuple<int, foo>
bar()
{
return std::make_tuple(2, foo{});
}
const auto t = bar();
210
tuple
permet de retourner plusieurs valeurs
sans avoir à créer un nouveau type (2)
struct foo {};
std::tuple<int, foo>
bar()
{
return std::make_tuple(2, foo{});
}
auto i = 0;
auto f = foo{};
std::tie(i, f) = bar();
std::cout << i; // 2
211
tuple
concaténer des tuples
auto t0 = std::make_tuple(1, foo{}, 'a');
std::cout << std::tuple_size<decltype(t0)>::value; // 3
auto t1 = std::make_tuple(3.14, std::string{"abc"});
std::cout << std::tuple_size<decltype(t1)>::value; // 2
auto t2 = std::tuple_cat(t0, t1);
std::cout << std::tuple_size<decltype(t2)>::value; // 5
212
unordered_map
table de hachage
auto map = std::unordered_map<int, std::string>{};
map.insert({42, "abc"});
map.emplace(33, "def");
for (const auto& key_value : map)
{
std::cout << key_value.first << " -> " << key_value.second;
}
fonctionne comme std::map
213
unordered_map
fonctions spécifiques aux tables de hachage
auto map = std::unordered_map<int, std::string>{1'000'000};
peut-être initialisée avec un nombre de buckets
map.rehash(2'000'000);
peut-être redimensionnée
map.load_factor()
afficher le facteur de charge
214
unordered_map
utiliser un type personnalisé en tant que clé
namespace std
{
template <>
struct hash<foo>
{
std::size_t
operator()(const foo& f)
const
{
// hash function
}
};
}
bool operator==(const foo&, const foo&);
struct foo
{
// some data
};
clé
égalité
hachage
214
unordered_map
utiliser un type personnalisé en tant que clé
namespace std
{
template <>
struct hash<foo>
{
std::size_t
operator()(const foo& f)
const
{
// hash function
}
};
}
bool operator==(const foo&, const foo&);
struct foo
{
// some data
};
clé
égalité
hachage
spécialisation
215
unordered_map
bonus: combiner des valeurs de hachage (1)
/// @brief Combine the hash value of x with seed.
///
/// Taken from <boost/functional/hash.hpp>.
/// Sligthy modified to use std::hash<T> instead of
/// boost::hash_value().
template <typename T>
inline
void
hash_combine(std::size_t& seed, const T& x)
noexcept(noexcept(std::hash<T>()(x)))
{
seed ^= std::hash<T>()(x) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
216
unordered_map
bonus: combiner des valeurs de hachage (2)
namespace std
{
template <>
struct hash<foo>
{
std::size_t
operator()(const foo& f)
const
{
auto seed = 0ul;
hash_combine(seed, f.x);
hash_combine(seed, f.y);
return seed;
}
};
}
217
Gestion de la mémoire
• introduction de unique_ptr
‣ auto_ptr deprecated
‣ utilise les portées pour désallouer
• introduction de shared_ptr
‣ déjà présent dans Boost et POCCO
‣ comptage de référence
• désallocation automatique dans tous les cas
218
auto_ptr
struct foo
{
~foo(){std::cout << "~foo()n";}
};
int main()
{
{
auto ptr1 = std::auto_ptr<foo>{new foo};
std::cout << ptr1.get() << 'n';
}
std::cout << "after scopen";
}
désallocation automatique à la sortie la portée
0x7fc1e1c00080
~foo()
after scope
218
auto_ptr
struct foo
{
~foo(){std::cout << "~foo()n";}
};
int main()
{
{
auto ptr1 = std::auto_ptr<foo>{new foo};
std::cout << ptr1.get() << 'n';
}
std::cout << "after scopen";
}
désallocation automatique à la sortie la portée
0x7fc1e1c00080
~foo()
after scope
désallocation ici
219
auto_ptr
auto ptr1 = std::auto_ptr<int>{new int{42}};
std::cout << ptr1.get(); // 0x7ff01a400080
auto ptr2 = ptr1;
std::cout << ptr1.get(); // 0x0
std::cout << ptr2.get(); // 0x7ff01a400080
le problème (1)
ptr2 est une copie de ptr1
et pourtant ce dernier perd la propriété du pointeur
sémantique peu claire!
220
auto_ptr
auto vec = std::vector<std::auto_ptr<foo>>{};
auto ptr = std::auto_ptr<foo>{new foo};
vec.push_back(ptr);
auto x = vec.front(); // vec.front() is now nullptr.
le problème (2)
avec un conteneur STL
221
unique_ptr
désallocation automatique à la sortie la portée
struct foo
{
~foo(){std::cout << "~foo()n";}
};
int main()
{
{
auto ptr1 = std::unique_ptr<foo>{new foo};
std::cout << ptr1.get() << 'n';
}
std::cout << "after scopen";
}
0x7fc1e1c00080
~foo()
after scope
221
unique_ptr
désallocation automatique à la sortie la portée
struct foo
{
~foo(){std::cout << "~foo()n";}
};
int main()
{
{
auto ptr1 = std::unique_ptr<foo>{new foo};
std::cout << ptr1.get() << 'n';
}
std::cout << "after scopen";
}
0x7fc1e1c00080
~foo()
after scope
désallocation ici
222
unique_ptr
résoudre le problème de auto_ptr
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = ptr1;
error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int,
std::__1::default_delete<int> >'
  auto ptr2 = ptr1;
       ^      ~~~~
interdire la copie
transfert de propriété par déplacement
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = std::move(ptr1); // transfert ownership
222
unique_ptr
résoudre le problème de auto_ptr
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = ptr1;
error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int,
std::__1::default_delete<int> >'
  auto ptr2 = ptr1;
       ^      ~~~~
!
interdire la copie
transfert de propriété par déplacement
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = std::move(ptr1); // transfert ownership
222
unique_ptr
résoudre le problème de auto_ptr
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = ptr1;
error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int,
std::__1::default_delete<int> >'
  auto ptr2 = ptr1;
       ^      ~~~~
!
interdire la copie
transfert de propriété par déplacement
auto ptr1 = std::unique_ptr<int>{new int{42}};
auto ptr2 = std::move(ptr1); // transfert ownership
un unique propriétaire du pointeur : ptr1, puis ptr2
223
unique_ptr
exemple de fabrique
struct foo {};
std::unique_ptr<foo>
factory()
{
return std::unique_ptr<foo>{new foo{}};
}
pas de problème de copie de foo
ou de responsabilité de foo*
224
unique_ptr
utilisation comme un pointeur normal
struct foo
{
int x;
};
auto ptr = std::unique_ptr<foo>(new foo{42});
*ptr = foo{33};
ptr->x = 2;
225
unique_ptr
C++14
auto ptr = std::unique_ptr<int>(new int{42});
Création d’un unique_ptr
C++11
auto ptr = std::make_unique<int>(42);
226
unique_ptr
auto a = std::make_unique<int[]>(3);
Pour un tableau de taille inconnue à la compilation
auto ptr = std::unique_ptr<int[]>{new int[3]};
ptr[0] = 1;
ptr[1] = 0;
ptr[2] = 3;
std::cout << ptr[1];
Utilisation
227
shared_ptr
ressource partagée par plusieurs propriétaires
struct foo
{
~foo(){std::cout << "~foo()n";}
};
int main()
{
auto ptr = std::shared_ptr<foo>(new foo{});
{
auto ptr2 = ptr;
}
std::cout << "scope exitn";
}
228
shared_ptr
A
Pointer
228
shared_ptr
A
Pointer
compteur de référence
1
228
shared_ptr
A B
Pointer
compteur de référence
12
228
shared_ptr
A B C
Pointer
compteur de référence
123
228
shared_ptr
A C
Pointer
compteur de référence
1232
228
shared_ptr
C
Pointer
compteur de référence
12321
228
shared_ptr
Pointer
compteur de référence
123210delete
229
shared_ptr
auto f1 = std::shared_ptr<foo>(new foo{});
auto f2 = std::make_shared<foo>();
objectif : plus un seul new dans le code
facilité de création : make_shared
230
shared_ptr
const auto ptr = std::make_shared<foo>();
const auto ptr = std::shared_ptr<foo>(new foo{});
performances
préférez
à
230
shared_ptr
const auto ptr = std::make_shared<foo>();
const auto ptr = std::shared_ptr<foo>(new foo{});
performances
préférez
à
1 allocation
230
shared_ptr
const auto ptr = std::make_shared<foo>();
const auto ptr = std::shared_ptr<foo>(new foo{});
performances
préférez
à
1 allocation
2 allocations
231
shared_ptr
utilisation comme un pointeur normal
struct foo
{
int x;
};
auto ptr = std::make_shared<foo>(42);
*ptr = foo{33};
ptr->x = 2;
232
shared_ptr
cycles
struct foo
{
std::shared_ptr<foo> link;
~foo(){std::cout << "~foon";}
};
auto f1 = std::make_shared<foo>();
auto f2 = std::make_shared<foo>();
f1->link = f2;
f2->link = f1;
233
shared_ptr
casser les cycles avec weak_ptr
struct foo
{
std::weak_ptr<foo> link;
~foo(){std::cout << "~foon";}
};
auto f1 = std::make_shared<foo>();
auto f2 = std::make_shared<foo>();
f1->link = f2;
f2->link = f1;
234
shared_ptr
utilisation de weak_ptr
struct foo
{
std::weak_ptr<foo> link;
void operator()() const
{
if (const auto shared = link.lock())
{
// ok, do something with shared
}
else
{
// link is no longer valid
}
}
};
234
shared_ptr
utilisation de weak_ptr
struct foo
{
std::weak_ptr<foo> link;
void operator()() const
{
if (const auto shared = link.lock())
{
// ok, do something with shared
}
else
{
// link is no longer valid
}
}
};
renvoie un shared_ptr
235
shared_ptr
accès au nombre de référencement
const auto ptr1 = std::shared_ptr<foo>(new foo{});
std::cout << ptr1.use_count(); // “1”
const auto ptr2 = ptr1;
std::cout << ptr1.use_count(); // “2”
std::cout << ptr2.use_count(); // “2”
236
shared_ptr
auto ptr = std::make_shared<int[3]>{new int[3]};
ne fonctionne pas avec les tableaux
!
auto ptr = std::make_shared<std::array<int, 3>>();
shared_ptr<array>
alternatives
std::vector
auto ptr = std::make_shared<std::vector<int>>();
237
shared_ptr
factory avec cache
struct foo { ~foo(){std::cout << “~foo()";} };
std::shared_ptr<foo> mk_foo(int x)
{
// ???
}
int main()
{
const auto foo_ptr1 = mk_foo(42);
const auto foo_ptr2 = mk_foo(42);
std::cout << "main exitn";
}
237
shared_ptr
factory avec cache
struct foo { ~foo(){std::cout << “~foo()";} };
std::shared_ptr<foo> mk_foo(int x)
{
static auto cache = std::unordered_map<int, std::shared_ptr<foo>>{};
const auto search = cache.find(x);
if (search == end(cache))
{
const auto tmp = std::make_shared<foo>();
return cache.emplace(x, tmp).first->second;
}
else
{
return search->second;
}
}
int main()
{
const auto foo_ptr1 = mk_foo(42);
const auto foo_ptr2 = mk_foo(42);
std::cout << "main exitn";
}
238
shared_ptr/unique_ptr
Propriétaires 1 n ?
unique_ptr ✔
shared_ptr ✔ ✔ ✔
en résumé
239
shared_ptr/unique_ptr
exception-safe code
try
{
const auto ptr = new int{42};
foo(ptr);
delete ptr;
}
catch (...)
{}
fuite mémoire
240
shared_ptr/unique_ptr
exception-safe code
try
{
const auto ptr = std::make_shared<int>(42);
foo(ptr);
}
catch (...)
{}
fuite mémoire évitée
241
shared_ptr/unique_ptr
struct bar {bar() {throw std::runtime_error("");}};
struct foo
{
int* i_;
bar br_;
foo()
: i_{new int{42}}
, br_{}
{}
~foo() {delete i_;}
};
try
{
const auto f = foo{};
}
catch (...)
{}
exception-safe code, cas des constructeurs (1)
241
shared_ptr/unique_ptr
struct bar {bar() {throw std::runtime_error("");}};
struct foo
{
int* i_;
bar br_;
foo()
: i_{new int{42}}
, br_{}
{}
~foo() {delete i_;}
};
try
{
const auto f = foo{};
}
catch (...)
{}
jamais appelé
exception-safe code, cas des constructeurs (1)
242
shared_ptr/unique_ptr
exception-safe code, cas des constructeurs (2)
struct bar {bar() {throw std::runtime_error("");}};
struct foo
{
std::shared_ptr<int> i_;
bar br_;
foo()
: i_{std::make_shared<int>(42)}
, br_{}
{}
~foo() {} // never called
};
try
{
const auto f = foo{};
}
catch (...)
{}
243
shared_ptr/unique_ptr
exception-safe code, cas des constructeurs (3)
struct foo
{
int* i_;
bar br_;
foo()
try
: i_{new int{42}}
, br_{}
{}
catch (...)
{
delete i_;
}
~foo() {delete i_;} // never called
};
autre solution…
244
function
pour remplacer les pointeurs de fonction
int bar(int x) {return x * 3;}
struct foo
{
int operator()(int x) const {return x + 1;}
};
using function_type = std::function<int (int)>;
// free function
auto fun1 = function_type{bar};
// function object
auto fun2 = function_type{foo{}};
// lambda
auto fun3 = function_type{[](int x){return x - 1;}};
std::cout << fun1(3) << ‘n'; // 9
std::cout << fun2(3) << ‘n'; // 4
std::cout << fun3(3) << ‘n'; // 2
244
function
pour remplacer les pointeurs de fonction
int bar(int x) {return x * 3;}
struct foo
{
int operator()(int x) const {return x + 1;}
};
using function_type = std::function<int (int)>;
// free function
auto fun1 = function_type{bar};
// function object
auto fun2 = function_type{foo{}};
// lambda
auto fun3 = function_type{[](int x){return x - 1;}};
std::cout << fun1(3) << ‘n'; // 9
std::cout << fun2(3) << ‘n'; // 4
std::cout << fun3(3) << ‘n'; // 2
type de retour
244
function
pour remplacer les pointeurs de fonction
int bar(int x) {return x * 3;}
struct foo
{
int operator()(int x) const {return x + 1;}
};
using function_type = std::function<int (int)>;
// free function
auto fun1 = function_type{bar};
// function object
auto fun2 = function_type{foo{}};
// lambda
auto fun3 = function_type{[](int x){return x - 1;}};
std::cout << fun1(3) << ‘n'; // 9
std::cout << fun2(3) << ‘n'; // 4
std::cout << fun3(3) << ‘n'; // 2
type de retour
paramètres
245
function
les fonctions deviennent des valeurs comme les autres
enum class op_type{add, diff};
// A function factory
std::function<int (int)>
mk_fun(op_type op, int value)
{
switch (op)
{
case op_type::add : return [=](int x){return value + x;};
case op_type::diff: return [=](int x){return value - x;};
}
}
const auto fun0 = mk_fun(op_type::add, 1);
std::cout << fun0(1) << ‘n'; // 2
const auto fun1 = mk_fun(op_type::diff, 1);
std::cout << fun1(1) << ‘n'; // 0
“programmation fonctionnelle”
245
function
les fonctions deviennent des valeurs comme les autres
enum class op_type{add, diff};
// A function factory
std::function<int (int)>
mk_fun(op_type op, int value)
{
switch (op)
{
case op_type::add : return [=](int x){return value + x;};
case op_type::diff: return [=](int x){return value - x;};
}
}
const auto fun0 = mk_fun(op_type::add, 1);
std::cout << fun0(1) << ‘n'; // 2
const auto fun1 = mk_fun(op_type::diff, 1);
std::cout << fun1(1) << ‘n'; // 0
“programmation fonctionnelle”
capture par copie
246
function
fonctions membres
struct baz
{
int do_it(int x, int y) const {return x + y;}
};
auto fun4 = std::function<int (const baz&, int, int)>{&baz::do_it};
const auto b = baz{};
// same as b.do_it(3, 4)
std::cout << fun4(b, 3, 4); // 7
246
function
fonctions membres
struct baz
{
int do_it(int x, int y) const {return x + y;}
};
auto fun4 = std::function<int (const baz&, int, int)>{&baz::do_it};
const auto b = baz{};
// same as b.do_it(3, 4)
std::cout << fun4(b, 3, 4); // 7
this
247
bind
int add(int x, int y)
{
return x + y;
}
using namespace std::placeholders; // _1, _2, etc.
const auto fun = std::bind(add, 1, _1);
std::cout << fun(2); // 3
pour “lier” (fixer) des paramètres
247
bind
int add(int x, int y)
{
return x + y;
}
using namespace std::placeholders; // _1, _2, etc.
const auto fun = std::bind(add, 1, _1);
std::cout << fun(2); // 3
pour “lier” (fixer) des paramètres
x est fixé à 1
247
bind
int add(int x, int y)
{
return x + y;
}
using namespace std::placeholders; // _1, _2, etc.
const auto fun = std::bind(add, 1, _1);
std::cout << fun(2); // 3
pour “lier” (fixer) des paramètres
x est fixé à 1 y reste libre
247
bind
int add(int x, int y)
{
return x + y;
}
using namespace std::placeholders; // _1, _2, etc.
const auto fun = std::bind(add, 1, _1);
std::cout << fun(2); // 3
pour “lier” (fixer) des paramètres
x est fixé à 1 y reste libre
2 est passé à y
248
bind
int add(int x, int y)
{
return x + y;
}
const auto fun = [](int z){return add(1, z);};
std::cout << fun(2); // 3
lambda équivalent
248
bind
int add(int x, int y)
{
return x + y;
}
const auto fun = [](int z){return add(1, z);};
std::cout << fun(2); // 3
lambda équivalent
x est fixé à 1
249
bind
struct foo
{
int operator()(int x, int y)
const
{return x + y;}
};
using namespace std::placeholders;
const auto f = foo{};
const auto fun = std::bind(&foo::operator(), f, 3, _1);
std::cout << fun(2);
pour lier une méthode à un objet
250
bind
les paramètres sont passés par copie
void baz(int& x)
{
++x;
}
auto i = 0;
auto fun = std::bind(baz, i);
std::cout << i; // 0
fun(); // call baz()
std::cout << i; // 0
250
bind
les paramètres sont passés par copie
void baz(int& x)
{
++x;
}
auto i = 0;
auto fun = std::bind(baz, i);
std::cout << i; // 0
fun(); // call baz()
std::cout << i; // 0
par valeur
251
bind
void baz(int& x)
{
++x;
}
auto i = 0;
auto fun = std::bind(baz, std::ref(i));
std::cout << i; // 0
fun(); // call baz()
std::cout << i; // 1
modifier les paramètres fixés
251
bind
void baz(int& x)
{
++x;
}
auto i = 0;
auto fun = std::bind(baz, std::ref(i));
std::cout << i; // 0
fun(); // call baz()
std::cout << i; // 1
modifier les paramètres fixés
par référence
252
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<foo&>{f0, f1};
comment créer un conteneur de références? (1)
252
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<foo&>{f0, f1};
ne compile pas
comment créer un conteneur de références? (1)
253
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<foo*>{&f0, &f1};
comment créer un conteneur de références? (2)
253
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<foo*>{&f0, &f1};
comment créer un conteneur de références? (2)
il faut utiliser un pointeur
254
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1};
for (auto& f : vec)
{
f.get().data = 5;
}
std::cout << f0.data << " " << f1.data << ‘n'; // 5 5
comment créer un conteneur de références? (3)
255
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1};
for (foo& f : vec)
{
f.data = 5;
}
std::cout << f0.data << " " << f1.data << ‘n'; // 5 5
comment créer un conteneur de références? (4)
255
reference_wrapper
struct foo
{
int data;
};
auto f0 = foo{0};
auto f1 = foo{1};
auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1};
for (foo& f : vec)
{
f.data = 5;
}
std::cout << f0.data << " " << f1.data << ‘n'; // 5 5
comment créer un conteneur de références? (4)
conversion vers foo
256
enable_if
sélectionner statiquement une fonction
template <bool Cond>
typename std::enable_if<Cond, void>::type
foo()
{
std::cout << "Condn";
}
template <bool Cond>
typename std::enable_if<not Cond, void>::type
foo()
{
std::cout << "not Condn";
}
foo<true>(); // Cond
foo<false>(); // not Cond
256
enable_if
sélectionner statiquement une fonction
template <bool Cond>
typename std::enable_if<Cond, void>::type
foo()
{
std::cout << "Condn";
}
template <bool Cond>
typename std::enable_if<not Cond, void>::type
foo()
{
std::cout << "not Condn";
}
foo<true>(); // Cond
foo<false>(); // not Cond
type de retour
type de retour
257
enable_if
écriture simplifiée
template <bool Cond>
enable_if_t<Cond, void>
foo()
{
std::cout << "Condn";
}
template <bool Cond>
enable_if_t<not Cond, void>
foo()
{
std::cout << "not Condn";
}
foo<true>(); // Cond
foo<false>(); // not Cond
template <bool Cond, typename Res>
using enable_if_t = typename std::enable_if<Cond, Res>::type;
257
enable_if
écriture simplifiée
template <bool Cond>
enable_if_t<Cond, void>
foo()
{
std::cout << "Condn";
}
template <bool Cond>
enable_if_t<not Cond, void>
foo()
{
std::cout << "not Condn";
}
foo<true>(); // Cond
foo<false>(); // not Cond
template <bool Cond, typename Res>
using enable_if_t = typename std::enable_if<Cond, Res>::type;
défini en C++14
258
type_traits
• trait : un petit objet dont le but est de contenir des informations
à propos d’autres types
‣ pour connaître des détails d’implantation
‣ pour déterminer des politiques d’implantation
259
type_traits
std::cout << std::boolalpha;
std::cout << std::is_pointer<int*>::value << ‘n'; // true
std::cout << std::is_pointer<int>::value << ‘n'; // false
std::cout << std::is_class<foo>::value << ‘n'; // true
std::cout << std::is_class<int>::value << ‘n'; // false
std::cout << std::is_signed<int>::value << ‘n'; // true
std::cout << std::is_signed<unsigned int>::value << ‘n'; // false
259
type_traits
std::cout << std::boolalpha;
std::cout << std::is_pointer<int*>::value << ‘n'; // true
std::cout << std::is_pointer<int>::value << ‘n'; // false
std::cout << std::is_class<foo>::value << ‘n'; // true
std::cout << std::is_class<int>::value << ‘n'; // false
std::cout << std::is_signed<int>::value << ‘n'; // true
std::cout << std::is_signed<unsigned int>::value << ‘n'; // false
etc.
260
type_traits
template <typename T>
enable_if_t<std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << x;
}
template <typename T>
enable_if_t<not std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << '"' << x << '"';
}
json(42); // 42
json(“42"); // “42”
json(“abc”); // “abc”
260
type_traits
template <typename T>
enable_if_t<std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << x;
}
template <typename T>
enable_if_t<not std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << '"' << x << '"';
}
json(42); // 42
json(“42"); // “42”
json(“abc”); // “abc”
exemple : export JSON
260
type_traits
template <typename T>
enable_if_t<std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << x;
}
template <typename T>
enable_if_t<not std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << '"' << x << '"';
}
json(42); // 42
json(“42"); // “42”
json(“abc”); // “abc”
exemple : export JSON
si T n’est pas un type
numérique
260
type_traits
template <typename T>
enable_if_t<std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << x;
}
template <typename T>
enable_if_t<not std::is_integral<T>::value, void>
json(const T& x)
{
std::cout << '"' << x << '"';
}
json(42); // 42
json(“42"); // “42”
json(“abc”); // “abc”
exemple : export JSON
si T n’est pas un type
numérique
si T est un type
numérique
261
random
• Deux types de composants
‣ Générateurs de nombres aléatoires
- matériel
- pseudo-aléatoire
• en général, le matériel est utilisé en tant que seed pour
les générateurs pseudo-aléatoires
‣ Distributions statistiques
- uniforme, normal, Poisson, etc.
262
random
std::random_device rd;
std::cout << rd() << 'n';
std::cout << rd() << 'n';
std::uniform_int_distribution<int> uniform_dist{1, 5};
std::cout << uniform_dist(rd) << 'n';
std::cout << uniform_dist(rd) << 'n';
std::cout << uniform_dist(rd) << 'n';
exemple (1)
263
random
std::random_device rd;
// Seed engine with hardware
std::default_random_engine gen{rd()};
std::cout << gen() << 'n';
std::cout << gen() << 'n';
std::normal_distribution<> gaussian;
std::cout << gaussian(gen) << 'n';
std::cout << gaussian(gen) << 'n';
std::cout << gaussian(gen) << 'n';
exemple (2)
264
chrono
• Trois composants
‣ durées
‣ horloges
- système
- stable
- haute résolution
‣ points dans le temps
265
chrono
auto start = std::chrono::time_point<std::chrono::steady_clock>{};
auto end = std::chrono::time_point<std::chrono::steady_clock>{};
start = std::chrono::steady_clock::now();
// do something here
end = std::chrono::steady_clock::now();
const auto elapsed = end - start;
const auto s = std::chrono::duration<double>{elapsed};
const auto ms = std::chrono::duration<double, std::milli>{elapsed};
std::cout << "elapsed time: " << ms.count() << "msn";
std::cout << "elapsed time: " << s.count() << "sn";
exemple
265
chrono
auto start = std::chrono::time_point<std::chrono::steady_clock>{};
auto end = std::chrono::time_point<std::chrono::steady_clock>{};
start = std::chrono::steady_clock::now();
// do something here
end = std::chrono::steady_clock::now();
const auto elapsed = end - start;
const auto s = std::chrono::duration<double>{elapsed};
const auto ms = std::chrono::duration<double, std::milli>{elapsed};
std::cout << "elapsed time: " << ms.count() << "msn";
std::cout << "elapsed time: " << s.count() << "sn";
exemple
à favoriser pour mesurer des écoulements de temps
266
regex
const auto str = std::string{"abc ABC 123"};
const auto number = std::regex{"d+"};
if (std::regex_search(str, number))
{
std::cout << "Number foundn";
}
déterminer la présence d’une séquence
267
regex
const auto str = std::string{"abc ABC 123"};
const auto words = std::regex{"([a-zA-Z]+)"};
auto words_it = std::sregex_iterator{begin(str), end(str), words};
const auto words_end = std::sregex_iterator{};
for (; words_it != words_end; ++words_it)
{
std::cout << words_it->str() << 'n';
}
afficher les séquences trouvées
268
regex
const auto str = std::string{"abc ABC 123"};
const auto uppercase = std::regex{"[A-Z]"};
std::regex_replace( std::ostreambuf_iterator<char>(std::cout)
, begin(str), end(str), uppercase, "-");
std::cout << std::regex_replace(str, uppercase, "|$&|") << 'n';
remplacer du texte
269
regex
Some people, when confronted with a problem,
think “I know, I'll use regular expressions.”
Now they have two problems.
OPTIMISATION
Optimiser
271
• Que faut-il optimiser?
‣ algorithmes
‣ mémoire
• À quel prix?
‣ portabilité
‣ maintenance
‣ possible complication du code
• Systèmes embarqué ≠ serveur ≠ PC
• Compilateurs
• Profiler avant d’optimiser
Copy-elision
272
• Des copies peuvent-être évitées par le compilateur
‣ retour par valeur
‣ temporaire passé par valeur
‣ exception capturée par valeur
• A le droit d’affecter les potentiels effets de bords
Copy-elision
273
struct foo
{
int data;
foo(int d)
: data{d}
{std::cout << "foo(int)n";}
foo(const foo&)
{std::cout << "foo(const foo&)n";}
foo& operator=(const foo&)
{std::cout << "operator=(const foo&)n"; return *this;}
foo(foo&&)
{std::cout << "foo(foo&&)n";}
foo& operator=(foo&&)
{std::cout << "operator=(foo&&)n"; return *this;}
};
structure témoin
foo
named_return_by_value()
{
auto f = foo{3};
f.data = 42;
return f;
}
foo
named_return_by_value2(int x)
{
auto f = foo{0};
f.data = x > 10 ? 0 : 1;
return f;
}
const auto f0 = named_return_by_value();
std::cout << f0.data << ‘n';
const auto f1 = named_return_by_value2();
std::cout << f1.data << ‘n';
Copy-elision
274
retour d’un objet nommé par valeur
foo
named_return_by_value()
{
auto f = foo{3};
f.data = 42;
return f;
}
foo
named_return_by_value2(int x)
{
auto f = foo{0};
f.data = x > 10 ? 0 : 1;
return f;
}
const auto f0 = named_return_by_value();
std::cout << f0.data << ‘n';
const auto f1 = named_return_by_value2();
std::cout << f1.data << ‘n';
Copy-elision
274
retour d’un objet nommé par valeur
pas de copie
pas de copie
Copy-elision
275
void pass_by_value(foo);
pass_by_value(foo{3});
passage d’un temporaire par valeur
Copy-elision
275
void pass_by_value(foo);
pass_by_value(foo{3});
passage d’un temporaire par valeur
pas de copie
Copy-elision
276
foo
different_return_values(int x)
{
const auto f0 = foo{0};
const auto f1 = foo{1};
return (x < 10) ? f0 : f1;
}
const auto f = different_return_values(10);
il faut renvoyer le même objet nommé
Copy-elision
276
foo
different_return_values(int x)
{
const auto f0 = foo{0};
const auto f1 = foo{1};
return (x < 10) ? f0 : f1;
}
const auto f = different_return_values(10);
il faut renvoyer le même objet nommé
copie
deux objets différents
C++11
277
• Optimisations “gratuites” en passant au C++11
• Rvalues references
• Utilisation de std::move
‣ si la copy-elision échoue
‣ réallocation sur un push_back()
‣ …
C++11
278
struct foo {
foo() {std::cout << "foo()n";}
foo(const foo&) {std::cout << "foo(const foo&)n";}
foo& operator=(const foo&) {std::cout << "operator=(const foo&)n"; return *this;}
#if __cplusplus >= 201103L
foo(foo&&) {std::cout << "foo(foo&&)n";}
foo& operator=(foo&&) {std::cout << "operator=(foo&&)n"; return *this;}
#endif
};
std::vector<foo> fun() {
std::vector<foo> vec;
vec.push_back(foo());
return vec;
}
std::vector<foo> vec;
vec = fun();
C++03 C++11
foo()
foo(foo&&)
foo()
foo(const foo&)
foo(const foo&)
foo()
foo(const foo&)
gcc/clang
Visual Studio
exemple
Performances CPU/RAM
279
Slide 17
CPU/Memory performance
Computer architecture: a quantitative approach
By John L. Hennessy, David A. Patterson, Andrea C. Arpaci-Dusseau
©Tony Albrecht - Pitfalls of Object Oriented Programming
Mémoire contigüe
280
int tab[2][3]
i+1 i+2
j+1 j+2 j+3 j+1 j+2 j+3
i j
Allocations
281
• Les allocations mémoires dans le tas sont coûteuses
‣ l’allocateur doit trouver un emplacement libre et de taille
suffisante en mémoire
‣ ressource abondante
• Privilégier les allocations sur la pile
‣ coûte seulement un déplacement du pointeur de pile
‣ mais c’est une ressource limitée
• Systèmes embarqués
282
.reserve()
petit rappel…
auto vec = std::vector<int>{};
for (auto i = 0ul; i < 8192; ++i)
{
vec.push_back(i);
}
auto vec = std::vector<int>{};
vec.reserve(8192);
for (auto i = 0ul; i < 8192; ++i)
{
vec.push_back(i);
}
0.044 ms 0.014 ms
283
.reserve()
si la taille n’est vraiment pas estimable à l’avance : std::deque
auto vec = std::vector<int>{};
for (auto i = 0ul; i < 8192; ++i)
{
vec.push_back(i);
}
auto vec = std::vector<int>{};
vec.reserve(8192);
for (auto i = 0ul; i < 8192; ++i)
{
vec.push_back(i);
}
0.044 ms 0.014 ms
auto deq = std::deque<int>{};
for (auto i = 0ul; i < 8192; ++i)
{
deq(i);
}
0.022 ms(gcc, clang)
Réutilisation d’objets alloués
284
for (auto i = 0ul; i < 1000; ++i)
{
auto ptr = std::make_shared<int>();
*ptr = i;
}
auto ptr = std::make_shared<int>();
for (auto i = 0ul; i < 1000; ++i)
{
*ptr = i;
}
autre petit rappel…
0.002 ms 0.059 ms
Éviter les allocations répétées d’un même type
285
auto mem = std::vector<foo>(1000);
auto foos = std::vector<foo*>{};
foos.reserve(1000);
for (auto i = 0ul; i < 1000; ++i)
{
foos.push_back(new foo);
}
0.001 ms 0.044 ms
Empty base class optimisation
286
struct func
{
void operator()() const noexcept {}
};
struct a
{
func f;
int x;
};
struct b
: public func
{
int x;
};
std::cout << sizeof(func) << ‘n'; // 1
std::cout << sizeof(a) << ‘n'; // 8
std::cout << sizeof(b) << ‘n'; // 4
ne pas gaspiller de l’espace mémoire
Empty base class optimisation
286
struct func
{
void operator()() const noexcept {}
};
struct a
{
func f;
int x;
};
struct b
: public func
{
int x;
};
std::cout << sizeof(func) << ‘n'; // 1
std::cout << sizeof(a) << ‘n'; // 8
std::cout << sizeof(b) << ‘n'; // 4
classe vide (pas de données)
ne pas gaspiller de l’espace mémoire
Empty base class optimisation
286
struct func
{
void operator()() const noexcept {}
};
struct a
{
func f;
int x;
};
struct b
: public func
{
int x;
};
std::cout << sizeof(func) << ‘n'; // 1
std::cout << sizeof(a) << ‘n'; // 8
std::cout << sizeof(b) << ‘n'; // 4
classe vide (pas de données)
EBO
ne pas gaspiller de l’espace mémoire
Optimisez pour la mémoire
287
Profilez le code
288
THREADS
290
Bibliothèque portable de threads
• Thread
• Mutex
• Async
• Futures/Promises
• Atomic
• Lock-free
‣ Modèle mémoire standardisé
291
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
std::cout << "waiting for t " << t.get_id() << std::endl;
t.join();
std::cout << "t has ended" << std::endl;
return 0;
}
création
291
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
std::cout << "waiting for t " << t.get_id() << std::endl;
t.join();
std::cout << "t has ended" << std::endl;
return 0;
}
fonction à appeler
création
291
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
std::cout << "waiting for t " << t.get_id() << std::endl;
t.join();
std::cout << "t has ended" << std::endl;
return 0;
}
fonction à appeler paramètres de la fonction
création
292
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
// t.join();
return 0;
}
libc++abi.dylib: terminating
attention à la terminaison
à la destruction d’un thread, celui-ci doit-être détaché ou terminé
292
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
// t.join();
return 0;
}
libc++abi.dylib: terminating
~thread()
attention à la terminaison
à la destruction d’un thread, celui-ci doit-être détaché ou terminé
293
thread
#include <chrono>
#include <iostream>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
auto t = std::thread{sleep, 1000};
t.detach();
return 0;
}
détacher un thread
294
mutex
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
#include <mutex>
void fun(std::mutex& mutex)
{
mutex.lock();
std::cout << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
mutex.unlock();
}
int main ()
{
std::mutex m;
std::thread t0{fun, std::ref(m)};
std::thread t1{std::bind(fun, std::ref(m))};
t0.join();
t1.join();
return 0;
}
294
mutex
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
#include <mutex>
void fun(std::mutex& mutex)
{
mutex.lock();
std::cout << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
mutex.unlock();
}
int main ()
{
std::mutex m;
std::thread t0{fun, std::ref(m)};
std::thread t1{std::bind(fun, std::ref(m))};
t0.join();
t1.join();
return 0;
}
un mutex ne peut pas être copié
295
mutex
void fun(std::mutex& mutex, int depth)
{
if (depth == 0) return;
mutex.lock();
fun(mutex, depth - 1);
mutex.unlock();
}
std::mutex m;
std::thread t0{fun, std::ref(m), 3};
quel est le problème?
296
recursive_mutex
void fun(std::recursive_mutex& mutex, int depth)
{
if (depth == 0) return;
mutex.lock();
fun(mutex, depth - 1);
mutex.unlock();
}
std::recursive_mutex m;
std::thread t0{fun, std::ref(m), 3};
verrouillage multiple par le même thread autorisé
297
timed_mutex
void fun(std::timed_mutex& mutex)
{
if (mutex.try_lock_for(std::chrono::milliseconds{500}))
{
std::cout << ":-)" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{1500});
mutex.unlock();
}
else
{
std::cout << ":-(" << std::endl;
}
}
mutex avec attente bornée
298
mutex
void fun(std::mutex& mutex)
{
try
{
mutex.lock();
sub_fun(); // throw std::runtime_error{""};
mutex.unlock();
}
catch (...)
{
mutex.unlock();
}
}
en cas d’exception…
298
mutex
void fun(std::mutex& mutex)
{
try
{
mutex.lock();
sub_fun(); // throw std::runtime_error{""};
mutex.unlock();
}
catch (...)
{
mutex.unlock();
}
}
et si on l’oublie? interblocage…
en cas d’exception…
299
lock_guard
try
{
std::lock_guard<std::mutex> lock{mutex};
sub_fun(); // throw std::runtime_error{""};
}
catch (...)
{}
déverrouillage automatique à la sortie de la portée
à privilégier
299
lock_guard
try
{
std::lock_guard<std::mutex> lock{mutex};
sub_fun(); // throw std::runtime_error{""};
}
catch (...)
{}
déverrouillage automatique à la sortie de la portée
~lock_guard() : mutex.unlock()
à privilégier
lock_guard() : mutex.lock()
300
unique_lock
plus général que lock_guard
void fun(std::mutex& mutex)
{
if (auto lock = std::unique_lock<std::mutex>(mutex, std::try_to_lock))
{
std::cout << ":-)" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds{2});
}
else
{
std::cout << ":-(" << std::endl;
}
}
std::mutex m;
std::thread t0{fun, std::ref(m)};
std::thread t1{fun, std::ref(m)};
t0.join();
t1.join();
300
unique_lock
plus général que lock_guard
void fun(std::mutex& mutex)
{
if (auto lock = std::unique_lock<std::mutex>(mutex, std::try_to_lock))
{
std::cout << ":-)" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds{2});
}
else
{
std::cout << ":-(" << std::endl;
}
}
std::mutex m;
std::thread t0{fun, std::ref(m)};
std::thread t1{fun, std::ref(m)};
t0.join();
t1.join();
tente de prendre le mutex
~unique_lock() : mutex.unlock()
301
unique_lock
avec un timed_mutex
const auto lock
= std::unique_lock<std::timed_mutex>{mutex, std::chrono::seconds{1}}
if (lock)
{
std::cout << ":-)" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds{1500});
}
else
{
std::cout << ":-(" << std::endl;
}
302
unique_lock
peut-être transféré
std::unique_lock<std::mutex>
lock(std::mutex& mutex)
{
return std::unique_lock<std::mutex>(mutex);
}
void fun(std::mutex& mutex)
{
const auto l = lock(mutex);
std::this_thread::sleep_for(std::chrono::milliseconds{1500});
std::cout << std::this_thread::get_id() << std::endl;
}
303
lock
struct foo
{
std::mutex mutex;
int data;
foo(int d) : mutex{}, data{d} {}
};
void swap(foo& lhs, foo& rhs) noexcept
{
std::lock_guard<std::mutex> lhs_lock{lhs.mutex};
std::lock_guard<std::mutex> rhs_lock{rhs.mutex};
std::swap(lhs.data, rhs.data);
}
foo f1{1}, f2{2};
std::thread t0{[&]{swap(f1, f2);}};
std::thread t1{swap, std::ref(f2), std::ref(f1)};
verrouiller plusieurs mutex à la fois (1)
quel est le problème?
304
lock
verrouiller plusieurs mutex à la fois (2)
struct foo
{
std::mutex mutex;
int data;
foo(int d) : mutex{}, data{d} {}
};
void swap(foo& lhs, foo& rhs) noexcept
{
std::lock(lhs.mutex, rhs.mutex);
std::lock_guard<std::mutex> lhs_lock{lhs.mutex, std::adopt_lock};
std::lock_guard<std::mutex> rhs_lock{rhs.mutex, std::adopt_lock};
std::swap(lhs.data, rhs.data);
}
foo f1{1}, f2{2};
std::thread t0{[&]{swap(f1, f2);}};
std::thread t1{swap, std::ref(f2), std::ref(f1)};
304
lock
verrouiller plusieurs mutex à la fois (2)
struct foo
{
std::mutex mutex;
int data;
foo(int d) : mutex{}, data{d} {}
};
void swap(foo& lhs, foo& rhs) noexcept
{
std::lock(lhs.mutex, rhs.mutex);
std::lock_guard<std::mutex> lhs_lock{lhs.mutex, std::adopt_lock};
std::lock_guard<std::mutex> rhs_lock{rhs.mutex, std::adopt_lock};
std::swap(lhs.data, rhs.data);
}
foo f1{1}, f2{2};
std::thread t0{[&]{swap(f1, f2);}};
std::thread t1{swap, std::ref(f2), std::ref(f1)};
garantit l’absence d’interblocage
305
condition_variable
attendre un événement
std::mutex mutex;
std::condition_variable cond;
int data;
bool produced = false;
void producer()
{
std::unique_lock<std::mutex> l{mutex};
data = 0;
produced = true;
cond.notify_one();
}
void consumer()
{
std::unique_lock<std::mutex> l{mutex};
cond.wait(l, []{return produced;});
++data;
}
305
condition_variable
attendre un événement
std::mutex mutex;
std::condition_variable cond;
int data;
bool produced = false;
void producer()
{
std::unique_lock<std::mutex> l{mutex};
data = 0;
produced = true;
cond.notify_one();
}
void consumer()
{
std::unique_lock<std::mutex> l{mutex};
cond.wait(l, []{return produced;});
++data;
}
éviter les réveils intempestifs
306
async
#include <future>
#include <chrono>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
std::async(std::launch::async, sleep, 1500);
return 0;
}
lancer une tâche asynchrone
*sauf avec Visual Studio 2013 : bug
peut créer un nouveau thread ou réutiliser un existant
306
async
#include <future>
#include <chrono>
#include <thread>
void sleep(std::size_t ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds{ms});
};
int main ()
{
std::async(std::launch::async, sleep, 1500);
return 0;
}
lancer une tâche asynchrone
bloque tant que la tâche asynchrone n’est pas finie*
*sauf avec Visual Studio 2013 : bug
peut créer un nouveau thread ou réutiliser un existant
307
future
pour récupérer le résultat d’une tâche asynchrone
int worker()
{
return 42;
};
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
307
future
pour récupérer le résultat d’une tâche asynchrone
int worker()
{
return 42;
};
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
appel bloquant
308
future
plusieurs manières d’attendre le résultat (1)
int worker();
auto future = std::async(std::launch::async, worker);
future.wait();
const auto res = future.get();
308
future
plusieurs manières d’attendre le résultat (1)
int worker();
auto future = std::async(std::launch::async, worker);
future.wait();
const auto res = future.get();
appel bloquant
309
future
plusieurs manières d’attendre le résultat (2)
void fun()
{
std::this_thread::sleep_for(std::chrono::seconds{2});
};
auto f = std::async(std::launch::async, fun);
if (f.wait_for(std::chrono::seconds{1}) == std::future_status::ready)
{
std::cout << ":-)n";
}
else
{
std::cout << ":-(n";
}
310
future
récupérer les exceptions d’une tâche asynchrone
int fun()
{
throw std::runtime_error(":-(");
return 42;
};
auto future = std::async(std::launch::async, fun);
try
{
const auto res = future.get();
}
catch (const std::exception& e)
{
std::cerr << e.what() << 'n';
}
310
future
récupérer les exceptions d’une tâche asynchrone
int fun()
{
throw std::runtime_error(":-(");
return 42;
};
auto future = std::async(std::launch::async, fun);
try
{
const auto res = future.get();
}
catch (const std::exception& e)
{
std::cerr << e.what() << 'n';
}
311
future
possède un état interne
int worker();
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
const auto res = future.get(); // error!
311
future
possède un état interne
int worker();
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
const auto res = future.get(); // error!
non constant
311
future
possède un état interne
int worker();
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
const auto res = future.get(); // error!
non constant
get() modifie
l’état de future
311
future
possède un état interne
int worker();
auto future = std::async(std::launch::async, worker);
// do something else...
// now wait for the result of the asynchronous task
const auto res = future.get();
const auto res = future.get(); // error!
non constant
get() modifie
l’état de future
résultat déjà consommé
312
future
exemple
#include <algorithm>
#include <future>
#include <iostream>
#include <numeric>
#include <vector>
template <typename InputIterator>
int parallel_sum(InputIterator beg, InputIterator end)
{
const auto len = std::distance(beg, end);
if(len < 1000) return std::accumulate(beg, end, 0);
const auto mid = beg + len/2;
auto future = std::async( std::launch::async
, parallel_sum<InputIterator>, mid, end);
const auto sum = parallel_sum(beg, mid);
return sum + future.get();
}
int main()
{
const auto v = std::vector<int>(10000, 1);
std::cout << parallel_sum(begin(v), end(v));
}
313
async
retour sur async : évaluation paresseuse
int worker()
{
return 42;
};
auto future = std::async(std::launch::deferred, worker);
// do something else...
// now launch the task
const auto res = future.get();
313
async
retour sur async : évaluation paresseuse
int worker()
{
return 42;
};
auto future = std::async(std::launch::deferred, worker);
// do something else...
// now launch the task
const auto res = future.get();
évaluation paresseuse dans le thread appelant
313
async
retour sur async : évaluation paresseuse
int worker()
{
return 42;
};
auto future = std::async(std::launch::deferred, worker);
// do something else...
// now launch the task
const auto res = future.get();
évaluation paresseuse dans le thread appelant
effectue l’évaluation
314
async vs appel de fonction
int fun(int x, int y);
void do_something_with_res(int){}
const auto res = fun(1,3);
do_something_with_res(res);
auto future = std::async(fun, 1, 3);
do_something_with_res(future.get());
314
async vs appel de fonction
int fun(int x, int y);
void do_something_with_res(int){}
const auto res = fun(1,3);
do_something_with_res(res);
auto future = std::async(fun, 1, 3);
do_something_with_res(future.get());
bloquant
314
async vs appel de fonction
int fun(int x, int y);
void do_something_with_res(int){}
const auto res = fun(1,3);
do_something_with_res(res);
auto future = std::async(fun, 1, 3);
do_something_with_res(future.get());
bloquant
possiblement bloquant
315
packaged_task
associe un std::future à std::function
int incr(int x) {return x + 1;}
auto task = std::packaged_task<int(int)>{incr};
auto future = task.get_future();
auto thread = std::thread{std::move(task), 0};
std::cout << future.get() << 'n';
thread.join();
plus de souplesses que std::async
ex. : pool de threads
316
promise
pendant de future
#include <future>
#include <iostream>
#include <thread>
void fun(std::promise<int> promise)
{
promise.set_value(0);
}
int main()
{
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
std::cout << future.get() << std::endl;
thread.join();
}
316
promise
pendant de future
#include <future>
#include <iostream>
#include <thread>
void fun(std::promise<int> promise)
{
promise.set_value(0);
}
int main()
{
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
std::cout << future.get() << std::endl;
thread.join();
}
non copiable
317
promise
producteur consommateur
promise.set_value(0);
future.get()
auto promise = std::promise<int>{};
auto future = promise.get_future();
1
2
3
4
cycle de vie
318
promise
ne rendre le résultat disponible qu’à la fin de l’exécution
#include <chrono>
#include <future>
#include <iostream>
#include <thread>
void fun(std::promise<int>&& promise)
{
promise.set_value_at_thread_exit(0);
std::this_thread::sleep_for(std::chrono::seconds{1});
}
int main()
{
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
std::cout << future.get() << std::endl;
thread.join();
}
319
promise
exceptions (1)
void fun(std::promise<int>&& promise)
{
try
{
throw std::runtime_error{":-("};
}
catch (...)
{
promise.set_exception(std::current_exception());
}
}
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
try
{
std::cout << future.get() << std::endl;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
thread.join();
319
promise
exceptions (1)
void fun(std::promise<int>&& promise)
{
try
{
throw std::runtime_error{":-("};
}
catch (...)
{
promise.set_exception(std::current_exception());
}
}
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
try
{
std::cout << future.get() << std::endl;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
thread.join();
positionne l’exception du future
320
promise
exceptions (2)
void fun(std::promise<int>&& promise)
{
auto e = std::runtime_error{":-("};
promise.set_exception(std::make_exception_ptr(e));
}
auto promise = std::promise<int>{};
auto future = promise.get_future();
auto thread = std::thread{fun, std::move(promise)};
try
{
std::cout << future.get() << std::endl;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
thread.join();
autre manière…
321
promise
• En résumé
‣ permet un contrôle fin sur l’obtention des résultats
‣ permet un contrôle fin sur les exceptions propagées
• std::promise
‣ côté producteur du résultat
• std::future
‣ côté consommateur du résultat
322
Hiérarchie
std::async
std::packaged_task
std::promise
contrôle
simplicité
std::future est créé par tous ces mécanismes
323
~future
le destructeur d’un future est bloquant si créé par std::async
int main()
{
auto task = std::packaged_task<void()>{fun};
auto future = task.get_future();
auto thread = std::thread{std::move(task)};
thread.detach();
} // exit immediately
mais pas pour std::packaged_task et std::promise
int main()
{
auto future = std::async(std::launch::async, incr);
} // wait for async completion
324
shared_future
partager un std::future entre plusieurs threads
auto ready_promise = std::promise<void>{};
auto ready_future = ready_promise.get_future().share();
// or auto ready_future =
std::shared_future<void>{ready_promise.get_future()};
auto t1 = std::thread([&]{ready_future.get();});
auto t2 = std::thread([&]{ready_future.get();});
ready_promise.set_value();
t1.join();
t2.join();
324
shared_future
partager un std::future entre plusieurs threads
auto ready_promise = std::promise<void>{};
auto ready_future = ready_promise.get_future().share();
// or auto ready_future =
std::shared_future<void>{ready_promise.get_future()};
auto t1 = std::thread([&]{ready_future.get();});
auto t2 = std::thread([&]{ready_future.get();});
ready_promise.set_value();
t1.join();
t2.join();
plusieurs get()
possibles sur le
même future
GÉNIE LOGICIEL
326
Héritage
• Comment naviguer dans la hiérarchie
• Héritage multiple
• Dangers de l’héritage
• Principe de substitution de Liskov
• Des alternatives à l’héritage
327
Naviguer dans la hiérarchie
struct base
{
void fun1(){std::cout << "base::fun1n";}
virtual void fun2(){std::cout << "base::fun2n";}
};
struct derived : base
{
int i = 42;
void fun2(){std::cout << "derived::fun2n";}
void fun3(){std::cout << "derived::fun3 " << i << "n";}
};
struct derived2 : base
{
void fun2(){std::cout << "derived2::fun2n";}
void fun4(){std::cout << "derived::fun4n";}
};
base* ptr = new derived;
if (const auto as_derived = dynamic_cast<derived*>(ptr))
as_derived->fun3(); // OK
base* ptr = new derived2;
auto as_derived = static_cast<derived*>(ptr);
as_derived->fun3(); // Oops!
vérification à
l’exécution
vérification à la
compilation
downcast
328
Naviguer dans la hiérarchie
dynamic_cast vs typeid
struct base{virtual ~base(){}};
struct d1 : base {};
struct d2 : d1 {};
base* ptr = new d2;
if (auto* as_d2 = dynamic_cast<d2*>(ptr))
{
// ...
}
if (typeid(*ptr) == typeid(d2))
{
auto* as_d2 = static_cast<d2*>(ptr);
// ...
}
typeid plus rapide que dynamic_cast
if (typeid(*ptr) == typeid(d1))
{
// typeid returns the dynamic type
// thus typeid(*ptr) != typeid(d1)
auto* as_d1 = static_cast<d1*>(ptr);
}
if (auto* as_d1 = dynamic_cast<d1*>(ptr))
{
// OK
}
mais…
329
Naviguer dans la hiérarchie
en présence de shared_ptr
struct base
{
void fun1(){std::cout << "base::fun1n";}
virtual void fun2(){std::cout << "base::fun2n";}
};
struct derived : base
{
int i = 42;
void fun2(){std::cout << "derived::fun2n";}
void fun3(){std::cout << "derived::fun3 " << i << "n";}
};
struct derived2 : base
{
void fun2(){std::cout << "derived2::fun2n";}
void fun4(){std::cout << "derived::fun3n";}
};
auto ptr = std::shared_ptr<base>(new derived);
if (const auto as_derived = std::dynamic_pointer_cast<derived>(ptr))
as_derived->fun3();
auto ptr = std::shared_ptr<base>(new derived2);
auto as_derived = std::static_pointer_cast<derived>(ptr);
as_derived->fun3(); // Oops!
330
Prévoir l’héritage
struct foo
{
foo(){std::cout << "foo()n";}
~foo(){std::cout << "~foo()n";}
};
struct base
{
~base() {std::cout << "~base()n";}
};
struct derived : base
{
foo f;
~derived() {std::cout << "~derived()n";}
};
base* ptr = new derived;
delete ptr; // Undefined behavior
foo()
~base()en général :
destructeur
non virtuel
destructeur virtuel
effacement à
travers le type
de base
331
Prévoir l’héritage
struct base
{
protected:
~base() {std::cout << "~base()n";}
};
struct derived : base
{
~derived() {std::cout << "~derived()n";}
};
base* b = new derived;
delete b; // compilation error
derived* d = new derived;
delete d;
empêcher l’effacement à travers le type de base
impossible sur
le type de base
ok sur le type
concret
332
Prévoir l’héritage
pour une classe destinée à être héritée,
le destructeur doit-être :
public & virtual
protected & non-virtual
ou
333
Prévoir l’héritage
struct base final
{
};
struct derived : base
{
};
rappel C++11 : interdire l’héritage
error: base 'base' is marked 'final'
334
Héritage multiple
problème du “diamant”
struct A
{
void fun(){}
};
struct B : A {};
struct C : A {};
struct D : B, C {};
auto d = D{};
d.fun(); // which fun is it?erreur
335
Héritage multiple
problème du “diamant”
A
fun()
B C
D
335
Héritage multiple
problème du “diamant”
A
fun()
B C
D
A
fun()
336
Héritage virtuel
éviter le problème du “diamant”
struct A
{
void fun(){}
};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};
auto d = D{};
d.fun(); // OK!
337
Dangers de l’héritage
• Couplage fort
‣ relation de couplage la plus forte après friend
‣ “fragile base class”
• Possible impact sur les invariants
• Slicing
338
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
data.insert(end(data), begin(v), end(v));
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 3
338
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
data.insert(end(data), begin(v), end(v));
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 3
1
338
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
data.insert(end(data), begin(v), end(v));
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 3
1
2
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
2
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
2
3
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
2
3
4
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
2
3
4
le code client est cassé
par la mise à jour de la
classe de base!
339
Héritage : couplage fort
struct values {
std::vector<int> data;
~values() {}
virtual void add(const std::vector<int>& v) {
// data.insert(end(data), begin(v), end(v));
for (auto x : v) add(x);
}
virtual void add(int v) {
data.push_back(v);
}
};
struct derived_values final : values {
std::size_t counter = 0;
void add(const std::vector<int>& v) override {
counter += v.size();
values::add(v);
}
void add(int v) override {
counter += 1;
values::add(v);
}
};
auto ptr = std::unique_ptr<values>(new derived_values);
ptr->add({1,2,3});
std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
1
2
3
4
le code client est cassé
par la mise à jour de la
classe de base!
“fragile base class”
340
Héritage : invariants
struct list
{
std::list<int> data;
void push_front(int x) {data.push_front(x);}
void pop_front() {data.pop_front();}
void push_back(int x) {data.push_back(x);}
void pop_back() {data.pop_back();}
};
struct stack : list
{
void push(int x) {list::push_back(x);}
void pop() {list::pop_back();}
};
auto s = stack{};
s.push(3); s.push(4);
s.pop();
s.push_front(4); // !!!!
340
Héritage : invariants
struct list
{
std::list<int> data;
void push_front(int x) {data.push_front(x);}
void pop_front() {data.pop_front();}
void push_back(int x) {data.push_back(x);}
void pop_back() {data.pop_back();}
};
struct stack : list
{
void push(int x) {list::push_back(x);}
void pop() {list::pop_back();}
};
auto s = stack{};
s.push(3); s.push(4);
s.pop();
s.push_front(4); // !!!!
casse les invariants
340
Héritage : invariants
struct list
{
std::list<int> data;
void push_front(int x) {data.push_front(x);}
void pop_front() {data.pop_front();}
void push_back(int x) {data.push_back(x);}
void pop_back() {data.pop_back();}
};
struct stack : list
{
void push(int x) {list::push_back(x);}
void pop() {list::pop_back();}
};
auto s = stack{};
s.push(3); s.push(4);
s.pop();
s.push_front(4); // !!!!
casse les invariants
private
341
Slicing
attention au passage par valeur de types polymorphes
struct base
{
virtual void fun() const {std::cout << "base::fun()";}
};
struct derived : base
{
void fun() const {std::cout << "derived::fun()";}
};
void slice(const base ptr)
{
ptr.fun();
}
void no_slice(const base& ptr)
{
ptr.fun();
}
const auto d = derived{};
slice(d); // base::fun()
no_slice(d); // derived::fun()
341
Slicing
attention au passage par valeur de types polymorphes
struct base
{
virtual void fun() const {std::cout << "base::fun()";}
};
struct derived : base
{
void fun() const {std::cout << "derived::fun()";}
};
void slice(const base ptr)
{
ptr.fun();
}
void no_slice(const base& ptr)
{
ptr.fun();
}
const auto d = derived{};
slice(d); // base::fun()
no_slice(d); // derived::fun()
& manquant
342
Principe de substitution de Liskov
struct base
{
virtual void fun1() = 0;
virtual void fun2() = 0;
};
struct liskov : base
{
void fun1() {}
void fun2() {}
private:
void impl() {}
};
struct non_liskov : base
{
void fun1() {}
void fun2() {}
void fun3() {} // new behavior
};
base* ptr = new non_liskov;
if (auto p = dynamic_cast<non_liskov*>(ptr))
p->fun3();
342
Principe de substitution de Liskov
struct base
{
virtual void fun1() = 0;
virtual void fun2() = 0;
};
struct liskov : base
{
void fun1() {}
void fun2() {}
private:
void impl() {}
};
struct non_liskov : base
{
void fun1() {}
void fun2() {}
void fun3() {} // new behavior
};
contrat, interface
implémentation
ne peut plus être
substituée à une
autre implémentation
base* ptr = new non_liskov;
if (auto p = dynamic_cast<non_liskov*>(ptr))
p->fun3();
343
But de l’héritage
• Que cherche-t-on à faire?
• Réutiliser du code?
‣ pour factoriser
• Fournir une interface commune (contrat)?
‣ pour cacher l’implémentation
• Agréger des types disjoints?
‣ ex. : feuilles et nœuds d’un arbre
344
“Est-ce que je veux pouvoir substituer mon
implémentation à une autre?”
345
Des alternatives à l’héritage
• Composition
• Union discriminée
• Héritage statique
Composition
346
• Contenir une implémentation plutôt qu’en hériter
‣ une classe est la somme de comportements
‣ ex : contenir un objet de typeTCP ou UDP
‣ si une classe hérite deTCP, alors cela veut dire qu’elle peut se
substituer àTCP; est-ce vraiment cela que l’on souhaite?
• Permet de changer à la volée d’implémentation
‣ ex : auto f = foo{udp{}}; f.network() = tcp{};
Composition
347
// worker USES any kind of network
struct worker
{
std::shared_ptr<network> net_;
worker(const std::shared_ptr<network>&);
std::shared_ptr<network>& network();
void operator()() const;
};
auto x = worker{};
// we can invoke write on x;
// is this what we really want?
x.write();
// user can store worker as a network
network& n = i;
// but operator()() is lost
n(); // error
// a downcast is needed
dynamic_cast<worker&>(n).operator()();
auto t = std::make_shared<network>(tcp{});
auto x = worker{n};
// good, we can't invoke write() on x
x.write() // error
// we can change implementation on-the-fly
auto u = std::make_shared<network>(udp{});
x.network() = u;
// worker IS a tcp
struct worker : tcp
{
void operator()() const;
};
struct network{virtual void write() = 0;};
struct tcp : network;
struct udp : network;
héritage composition
Union discriminée
348
struct foo {
enum class ty {int_ty, tab_ty, double_ty} type;
union
{
int i;
int tab[10];
double d;
} u;
};
void print(const foo& f) {
switch (f.type) {
case foo::ty::int_ty :
std::cout << "int: " << f.u.i << 'n';break;
case foo::ty::tab_ty :
std::cout << "tab: " << f.u.tab[0] << 'n'; break;
case foo::ty::double_ty :
std::cout << "double : " << f.u.d << 'n'; break;
}
}
auto f = foo{};
f.type = foo::ty::int_ty;
f.u.i = 32;
print(f); // int: 32
f.type = foo::ty::double_ty;
f.u.d = 42.42;
print(f); // double: 42.42
facile avec les types primitifs
Union discriminée
349
généralisation avec Boost.Variant
struct foo{int x;};
struct bar {double x; double y;};
struct visitor
{
using result_type = void;
result_type operator()(const foo& f) const {
std::cout << "Foo " << f.x << ‘n';
}
result_type operator()(const bar& b) const {
std::cout << "Bar " << b.x << " " << b.y << ‘n';
}
};
using variant = boost::variant<foo, bar>;
const auto v0 = variant{foo{42}};
const auto v1 = variant{bar{1.0, 2.0}};
boost::apply_visitor(visitor{}, v0);
boost::apply_visitor(visitor{}, v1);
Héritage statique
350
utilisation du CRTP
template <typename Derived>
struct base
{
void common() const {std::cout << "common";}
void fun() const {static_cast<Derived*>(this)->fun_impl();}
};
struct derived_1 : public base<derived_1>
{
void fun_impl() const {std::cout << "derived_1::fun";}
};
struct derived_2 : public base<derived_2>
{
void fun_impl() const {std::cout << "derived_2::fun";}
};
const auto d1 = derived_1{};
const auto d2 = derived_2{};
d1.common(); // “common”
d2.common(); // “common”
d1.fun(); // “derived_1::fun”
d2.fun(); // “derived_2::fun”
351
Quelques techniques
• Injection de dépendances
• Interfaces non-virtuelles
• RAII
• Mixins
• Traits
• Type erasure
• PIMPL
352
Injection de dépendances
struct output {virtual void write(const std::string&) = 0;};
struct network : output {
network(const std::string& ip, unsigned int port);
virtual void write(const std::string&) override;
};
struct file : output {
file(const std::string& path);
virtual void write(const std::string&) override;
};
struct logger {
std::unique_ptr<output> out;
logger(const std::string& ip, unsigned int port);
logger(const std::string& path);
void operator()(const std::string& str);
};
auto lnet = logger{"192.168.0.1", 4242};
auto lfile = logger{"/path/to"};
352
Injection de dépendances
struct output {virtual void write(const std::string&) = 0;};
struct network : output {
network(const std::string& ip, unsigned int port);
virtual void write(const std::string&) override;
};
struct file : output {
file(const std::string& path);
virtual void write(const std::string&) override;
};
struct logger {
std::unique_ptr<output> out;
logger(const std::string& ip, unsigned int port);
logger(const std::string& path);
void operator()(const std::string& str);
};
comment tester?
auto lnet = logger{"192.168.0.1", 4242};
auto lfile = logger{"/path/to"};
352
Injection de dépendances
struct output {virtual void write(const std::string&) = 0;};
struct network : output {
network(const std::string& ip, unsigned int port);
virtual void write(const std::string&) override;
};
struct file : output {
file(const std::string& path);
virtual void write(const std::string&) override;
};
struct logger {
std::unique_ptr<output> out;
logger(const std::string& ip, unsigned int port);
logger(const std::string& path);
void operator()(const std::string& str);
};
comment tester? et si de nouveaux backends
sont disponibles?
avec les mêmes constructeurs?
auto lnet = logger{"192.168.0.1", 4242};
auto lfile = logger{"/path/to"};
353
Injection de dépendances
struct logger
{
std::unique_ptr<output> out;
logger(std::unique_ptr<output>&&);
void operator()(const std::string& str);
};
il suffit d’externaliser la construction des dépendances
auto impl = std::unique_ptr<output>{new file{"/path/to"}};
auto l = logger{std::move(fileimpl)};
pour tester, il suffit de créer un mock de output
353
Injection de dépendances
struct logger
{
std::unique_ptr<output> out;
logger(std::unique_ptr<output>&&);
void operator()(const std::string& str);
};
il suffit d’externaliser la construction des dépendances
auto impl = std::unique_ptr<output>{new file{"/path/to"}};
auto l = logger{std::move(fileimpl)};
pour tester, il suffit de créer un mock de output
“injection de dépendances”
354
Interfaces non-virtuelles
class interface
{
public:
~interface();
void process() {
// some pre stuff here
process_impl();
// some post stuff here
}
private:
// default implementation
// could be =0
virtual void process_impl();
};
class impl1 : public interface
{
virtual void process_impl();
};
class impl2 : public interface
{
virtual void process_impl();
};
354
Interfaces non-virtuelles
class interface
{
public:
~interface();
void process() {
// some pre stuff here
process_impl();
// some post stuff here
}
private:
// default implementation
// could be =0
virtual void process_impl();
};
class impl1 : public interface
{
virtual void process_impl();
};
class impl2 : public interface
{
virtual void process_impl();
};
permet de s’assurer
que des pre-et post
traitements sont
toujours effectués
peut fournir une
implémentation par
défaut
implémentation
privée, ne peut être
appelée par le code
client
355
Interfaces non-virtuelles
class xml : public base
{
void read_impl (istream&);
void write_impl (ostream&);
};
class json : public base
{
void read_impl (istream&);
void write_impl (ostream&);
};
class text : public base
{
void read_impl (istream&);
void write_impl (ostream&);
};
class base
{
public:
void read(istream& i)
{
lock_guard<mutex> lock{mutex_};
read_impl(i);
}
void write(ostream& o)
{
lock_guard<mutex> lock{mutex_};
write_impl(o);
}
virtual ~base() {}
private:
mutex mutex_;
some_data_to_protect data_;
virtual void read_impl(istream&) = 0;
virtual void write_impl(ostream&)= 0;
};
exemple : sérialisation d’une structure à protéger
356
RAII (Resource Acquisition is Initialization)
• Utiliser la sortie de portée et les destructeurs
• Bibliothèque standard
‣ unique_ptr
‣ shared_ptr
‣ lock_guard
‣ thread
• Code plus résistant
‣ aux exceptions
‣ aux oublis de fermetures de ressources
• Automatiser des tâches
357
RAII (Resource Acquisition is Initialization)
#include <cstdio>
#include <string>
struct file
{
std::string path;
std::FILE* fp;
file(const std::string p)
: path{p}, fp{std::fopen(p.c_str(), "w+")}
{}
~file() {std::fclose(fp);}
void write(const std::string& str) {std::fputs(str.c_str(), fp);}
};
int main()
{
auto f = file{"/Users/hal/Desktop/foo.txt"};
f.write("abc");
} // file is automatically closed
fermeture automatique de ressource
358
RAII (Resource Acquisition is Initialization)
struct timer {
std::chrono::time_point<std::chrono::steady_clock> start;
timer() : start(std::chrono::steady_clock::now()) // start timer
{}
~timer() // display duration on destruction
{
const auto end = std::chrono::steady_clock::now();
const auto duration = std::chrono::duration<double, std::milli>{end - start};
std::cout << duration.count() << "msn";
}
};
{
const auto _ = timer{};
const auto vec = std::vector<int>(10000);
} // ~timer(), displays something like 0.111545ms
{
const auto _ = timer{};
const auto string = std::string{"abc"};
} // ~timer(), displays something like 0.001886ms
automatiser des tâches
358
RAII (Resource Acquisition is Initialization)
struct timer {
std::chrono::time_point<std::chrono::steady_clock> start;
timer() : start(std::chrono::steady_clock::now()) // start timer
{}
~timer() // display duration on destruction
{
const auto end = std::chrono::steady_clock::now();
const auto duration = std::chrono::duration<double, std::milli>{end - start};
std::cout << duration.count() << "msn";
}
};
{
const auto _ = timer{};
const auto vec = std::vector<int>(10000);
} // ~timer(), displays something like 0.111545ms
{
const auto _ = timer{};
const auto string = std::string{"abc"};
} // ~timer(), displays something like 0.001886ms
automatiser des tâches
ne pas oublier d’utiliser une variable, sinon le destructeur est appelé immédiatement
359
Mixins
fragments de classes destinés à enrichir une classe
template<class T>
struct mixin1 : public T
{
void f() {std::cout << "mixin1::f()n"; T::f();}
};
template<class T>
struct mixin2 : public T
{
void g() {std::cout << "mixin2::g()n"; T::f();}
};
struct foo {
void f() {std::cout << "foo::f()n";};
};
// compose a new type, mixins act as decorators
using enhanced_type = mixin2<mixin1<foo>>;
auto x = enhanced_type{};
x.g();
360
Mixins
en utilisant le CRTP
template<class T>
struct mixin1
{
void fun1() {/* new behaviour */}
};
template<class T>
struct mixin2
{
void fun2()
{
// decorate an existing behaviour
static_cast<T*>(this)->fun();
}
};
// mixins are used as building blocks
struct foo : mixin1<foo>, mixin2<foo>
{
void fun() {std::cout << "foo::fun()n";};
};
auto f = foo{};
f.fun1();
f.fun2();
f.fun();
361
Mixins
conserver une interface commune
struct base
{
virtual ~base() {}
virtual std::unique_ptr<base> clone() const = 0;
};
template <typename Derived>
struct base_crtp : public base
{
std::unique_ptr<base> clone() const override
{
const auto& as_derived = static_cast<const Derived&>(*this);
return std::unique_ptr<base>{new Derived{as_derived}};
}
};
struct derived1 : public base_crtp<derived1> {};
struct derived2 : public base_crtp<derived2> {};
const auto ptr = std::unique_ptr<base>{new derived1};
const auto ptr2 = ptr->clone();
361
Mixins
conserver une interface commune
struct base
{
virtual ~base() {}
virtual std::unique_ptr<base> clone() const = 0;
};
template <typename Derived>
struct base_crtp : public base
{
std::unique_ptr<base> clone() const override
{
const auto& as_derived = static_cast<const Derived&>(*this);
return std::unique_ptr<base>{new Derived{as_derived}};
}
};
struct derived1 : public base_crtp<derived1> {};
struct derived2 : public base_crtp<derived2> {};
const auto ptr = std::unique_ptr<base>{new derived1};
const auto ptr2 = ptr->clone();
héritent de base
interface commune
mixin
362
Traits
template <typename T>
struct has_feature {
static const auto value = false;
};
template <>
struct has_feature<std::vector<int>> {
static const auto value = true;
};
template <bool HasFeature, typename T> struct algo_impl;
template <typename T> struct algo_impl<false, T> {
void operator()(const T&) const {std::cout << "O(n)n";}
};
template <typename T> struct algo_impl<true, T> {
void operator()(const T&) const {std::cout << "O(1)n";}
};
template <typename T>
void algo(const T& x) {
algo_impl<has_feature<T>::value, T>{}(x);
}
algo(std::vector<int>{});
algo(std::vector<long>{});
tirer parti de caractéristiques à la compilation
362
Traits
template <typename T>
struct has_feature {
static const auto value = false;
};
template <>
struct has_feature<std::vector<int>> {
static const auto value = true;
};
template <bool HasFeature, typename T> struct algo_impl;
template <typename T> struct algo_impl<false, T> {
void operator()(const T&) const {std::cout << "O(n)n";}
};
template <typename T> struct algo_impl<true, T> {
void operator()(const T&) const {std::cout << "O(1)n";}
};
template <typename T>
void algo(const T& x) {
algo_impl<has_feature<T>::value, T>{}(x);
}
algo(std::vector<int>{});
algo(std::vector<long>{});
trait général
trait spécialisé
algorithme général
algorithme spécialisé
dispatch statique
tirer parti de caractéristiques à la compilation
363
Traits
struct tag_1{};
struct tag_2{};
struct tag_3{};
template <typename T> struct traits {
using category = tag_1;
};
template <> struct traits<std::vector<int>> {
using category = tag_2;
};
template <typename T> void algo_impl(const T& x, tag_1) {
std::cout << "tag1n";
}
template <typename T> void algo_impl(const T& x, tag_2) {
std::cout << "tag2n";
}
template <typename T> void algo_impl(const T& x, tag_3) {
std::cout << "tag3n";
}
template <typename T> void algo(const T& x) {
algo_impl(x, typename traits<T>::category{});
}
tag dispatching
363
Traits
struct tag_1{};
struct tag_2{};
struct tag_3{};
template <typename T> struct traits {
using category = tag_1;
};
template <> struct traits<std::vector<int>> {
using category = tag_2;
};
template <typename T> void algo_impl(const T& x, tag_1) {
std::cout << "tag1n";
}
template <typename T> void algo_impl(const T& x, tag_2) {
std::cout << "tag2n";
}
template <typename T> void algo_impl(const T& x, tag_3) {
std::cout << "tag3n";
}
template <typename T> void algo(const T& x) {
algo_impl(x, typename traits<T>::category{});
}
tag dispatching
propriétés
spécialisation
spécialisation
spécialisation
trait par défaut
trait spécialisé
dispatch
364
Type erasure
struct foo
{
void fun1(int& x)
{
x += 1;
}
int fun2(int x)
{
return x * 2;
}
};
struct bar
{
void fun1(int& x)
{
x += 2;
}
int fun2(int x)
{
return x * 4;
}
};
scénario : deux classes avec la même interface
on aimerait les stocker dans le même conteneur sans
toucher aux classes (donc pas d’héritage)
auto vec = std::vector<???>{};
vec.emplace_back(foo{});
vec.emplace_back(bar{});
365
Type erasure
struct concept
{
virtual ~concept() {}
virtual void fun1(int&) = 0;
virtual int fun2(int) = 0;
};
template <typename T>
struct model final : public concept
{
T impl;
model(const T& x) : impl(x) {}
void fun1(int& x) override
{
impl.fun1(x);
}
int fun2(int x) override
{
return impl.fun2(x);
}
};
struct object
{
// type erasure
std::shared_ptr<concept> ptr;
template <typename T>
object(const T& x)
: ptr{std::make_shared<model<T>>(x)}
{}
void fun1(int& x)
{
ptr->fun1(x);
}
int fun2(int x)
{
return ptr->fun2(x);
}
};
une solution : effacer leur type
366
Type erasure
auto vec = std::vector<object>{};
vec.emplace_back(object{foo{}});
vec.emplace_back(object{bar{}});
auto o = object{foo{}};
std::cout << o.fun2(4) << 'n';
utilisation
367
Pointer to Implementation - PIMPL
réduire les dépendances à la compilation
class foo
{
public:
void fun1(int&);
private:
// implementation detail
data_type data;
};
main.cc
#include
foo.cc
#include
368
Pointer to Implementation - PIMPL
réduire les dépendances à la compilation
// forward declaration
class foo_impl;
class foo
{
public:
void fun1(int&);
private:
std::shared_ptr<foo_impl> pimpl;
};
main.cc
#include
class foo_impl
{
public:
void fun1(int&);
private:
// implementation detail
data_type data;
};
foo.cc
#include
Quelques lectures
369
• A tour of C++ - Bjarne Stroustrup
• Effective C++, Effective STL, Effective Modern C++ - Scott Meyers
• The C++ Standard Library - Nicolai Josuttis
• C++ Concurrency in Action - Anthony Williams
• Modern C++ Design - Andrei Alexandrescu

C++ 11/14

  • 1.
  • 2.
  • 3.
    C++11/14 3 • Nouveau langage •Possiblement des gains de performance juste en recompilant • rvalues references, move semantics • Quelques incompatibilités avec le standard précédent, mais bien souvent pour plus de sécurité ou de performances • ex. un destructeur ne peut plus lever d’exception par défaut (ce qui était déjà une mauvaise chose) • ex. règles de conversion plus strictes dans l’initialisation d’agrégats int tab[] = {1.0}; // compilation error
  • 4.
    4 ranged-base for loop auto constexpr static_assert rvaluesreferences enum class uniform initialization noexcept variadic templates relaxed POD definition nullptr unrestricted unions lambdas decltype trailing return type extern template final override >> user-defined literals alignas alignof
  • 5.
  • 6.
    Compilateurs 6 • Visual Studio2013 (incomplet) ‣ http://msdn.microsoft.com/en-us/library/hh567368.aspx ‣ plus gros manque : constexpr • Clang ≥ 3.3 ‣ http://clang.llvm.org/cxx_status.html • GCC ≥ 4.8 ‣ https://gcc.gnu.org/projects/cxx0x.html
  • 7.
  • 8.
  • 9.
    Boucles simplifiées 9 #include <iostream> #include<vector> int main() { std::vector<int> v; v.push_back(1); v.push_back(2); for ( std::vector<int>::const_iterator cit = v.begin() ; cit != v.end(); ++cit) { std::cout << *cit << ','; } } À l’ancienne : utilisation des itérateurs
  • 10.
    Boucles simplifiées 10 #include <iostream> #include<vector> int main() { std::vector<int> v; v.push_back(1); v.push_back(2); for (int i : v) { std::cout << i << ','; } } Syntaxe simplifiée
  • 11.
    Boucles simplifiées 10 #include <iostream> #include<vector> int main() { std::vector<int> v; v.push_back(1); v.push_back(2); for (int i : v) { std::cout << i << ','; } } Syntaxe simplifiée écriture naturelle
  • 12.
    Boucles simplifiées 11 { typedef std::vector<int>::const_iteratorcit_t; for ( cit_t __begin = v.begin(), __end = v.end() ; __begin != __end; ++__begin) { int i = *__begin; std::cout << i << ','; } } sous le capot :
  • 13.
    Boucles simplifiées 12 std::map<int, int>m; m.insert(std::make_pair(1,1)); for (std::map<int, int>::value_type key_value : m) { std::cout << key_value.first << ',' << key_value.second; } n’importe quel conteneur fournissant des itérateurs
  • 14.
    Boucles simplifiées 12 std::map<int, int>m; m.insert(std::make_pair(1,1)); for (std::map<int, int>::value_type key_value : m) { std::cout << key_value.first << ',' << key_value.second; } n’importe quel conteneur fournissant des itérateurs pas de ‘&’ →copie
  • 15.
    Boucles simplifiées 13 const inttab[] = {1,2,3}; for (int x : tab) { std::cout << x << 'n'; } tableaux C
  • 16.
    Boucles simplifiées 14 • Enrésumé, fonctionne pour ‣ les tableaux C ‣ tous les types qui fournissent - les fonctions membres begin(), end() - les fonctions libres begin(type), end(type)
  • 17.
    auto 15 #include <map> int main() { std::map<int,int> m; m.insert(std::make_pair(1,1)); for (std::pair<int, int>& key_value : m) { // do something with key_value } } où est le problème?
  • 18.
    auto 16 #include <map> int main() { std::map<int,int> m; m.insert(std::make_pair(1,1)); for (std::pair<const int, int>& key_value : m) { // ... } } où est le problème?
  • 19.
    auto 16 #include <map> int main() { std::map<int,int> m; m.insert(std::make_pair(1,1)); for (std::pair<const int, int>& key_value : m) { // ... } } où est le problème? la clef dans une std::map est constante
  • 20.
    auto 17 #include <map> int main() { std::map<int,int> m; m.insert(std::make_pair(1,1)); for (auto& key_value : m) { // ... } }
  • 21.
    auto 17 #include <map> int main() { std::map<int,int> m; m.insert(std::make_pair(1,1)); for (auto& key_value : m) { // ... } } le compilateur fait le travail pour nous : plus d’erreur possible
  • 22.
    18 auto #include <map> int main() { autom = std::map<int, int>(); m.insert(std::make_pair(1,1)); for (auto& key_value : m) { // do something with key_value } }
  • 23.
    18 auto #include <map> int main() { autom = std::map<int, int>(); m.insert(std::make_pair(1,1)); for (auto& key_value : m) { // do something with key_value } } pour toute variable
  • 24.
    19 auto m =std::map<int, int>(); for (const auto& key_value : m) { key_value.second = 2; // compilation error } auto auto utilise les règles de déduction des templates
  • 25.
    20 #include <vector> std::vector<int> fun() { //return something } int main() { const auto container = fun(); for (const auto& value : container) { // do something } } auto facilite la maintenance de code
  • 26.
    21 #include <deque> std::deque<int> fun() { //return something } int main() { const auto container = fun(); for (const auto& value : container) { // do something } } facilite la maintenance de code auto
  • 27.
    21 #include <deque> std::deque<int> fun() { //return something } int main() { const auto container = fun(); for (const auto& value : container) { // do something } } fun() change facilite la maintenance de code auto
  • 28.
    21 #include <deque> std::deque<int> fun() { //return something } int main() { const auto container = fun(); for (const auto& value : container) { // do something } } main() ne change pas! fun() change facilite la maintenance de code auto
  • 29.
    22 • permet d’adopterun style de programmation fonctionnelle ‣ Scala : val x = fun() ‣ C++ : const auto x = fun(); • auto utilise les règles de déduction des templates ‣ sauf pour le cas des listes d’initialisation (initializer_list) auto
  • 30.
    23 const auto x= foo(); void* _ = x; error: cannot initialize a variable of type 'void *' with an lvalue of type 'TYPE'   void* _ = x;         ^   ~ 1 error generated. auto connaître le type déduit (tout compilateur)
  • 31.
    Initialisation uniformisée 24 struct foo{}; struct bar { bar(foo){} }; int main() { // function declaration or variable definition? bar b(foo()); } “most vexing parse” • define variable b of type bar with an instance of foo; or • declare function b which returns a bar and takes a function with no parameter and which returns a foo
  • 32.
    25 Initialisation uniformisée struct foo{}; struct bar { bar(foo){} }; int main() { // add parentheses to declare a variable bar b((foo())); } “most vexing parse”
  • 33.
    26 struct foo {}; structbar { bar(foo){} }; int main() { // brace initializer bar b{foo{}}; // better auto b1 = bar{foo{}}; } solution : utiliser la syntaxe pour initialiser les agrégats : {} Initialisation uniformisée
  • 34.
    27 struct foo { int i_; foo(inti) : i_(i) {} }; struct bar { int i_; }; int main() { const auto f = foo{42}; const auto b = bar{42}; } pour construire un objet Initialisation uniformisée
  • 35.
    28 class foo { int i_; public: foo(inti) : i_(i) {} }; struct bar { int i_; }; int main() { const auto f = foo{42}; const auto b = bar{42}; } pour construire struct ou class Initialisation uniformisée
  • 36.
    29 struct foo { int i; }; foofun() { return {42}; } int main() { const auto f = fun(); } concision : on peut omettre le type de retour Initialisation uniformisée
  • 37.
    29 struct foo { int i; }; foofun() { return {42}; } int main() { const auto f = fun(); } concision : on peut omettre le type de retour pas de type explicite Initialisation uniformisée
  • 38.
    30 #include <map> int main() { autom = std::map<int,int>{}; // old style m.insert(std::make_pair(1, 2)); // new style m.insert({1, 2}); } exemple : insertion dans un conteneur associatif Initialisation uniformisée
  • 39.
    31 double a, b,c; int x{a + b + c}; int y(a + b + c); int z = a + b + c; ne compile pas : perte de précision OK (mais mal) idem plus de sécurité Initialisation uniformisée
  • 40.
    32 • Que choisir? •“{}” ‣ résistant au most vexing parse ‣ empêche la perte de précision ‣ utilisation plus cohérente • “()” ‣ ne casse pas les habitudes ‣ pas de confusion avec initializer_list (à suivre…) Initialisation uniformisée
  • 41.
    initializer_list 33 #include <iostream> #include <vector> intmain() { const auto vec = std::vector<int>{1,2,3}; for (const auto& x : vec) { std::cout << x; } } même syntaxe que pour initialiser les agrégats
  • 42.
    initializer_list 33 #include <iostream> #include <vector> intmain() { const auto vec = std::vector<int>{1,2,3}; for (const auto& x : vec) { std::cout << x; } } déclaration “naturelle” même syntaxe que pour initialiser les agrégats
  • 43.
    initializer_list 34 • C++11 définitun nouveau type ‣ std::initializer_list<T> ‣ #include <initializer_list> • Utilise la même syntaxe d'initialisation des tableaux C • {1,2,3} • Le contenu de la liste est copié
  • 44.
    initializer_list 35 #include <initializer_list> int main() { //deduced to std::initializer_list<int> const auto list = {1,2,2}; for (auto x : list) { // ... } } exception : auto + {} initializer_list auto utilise la déduction des templates
  • 45.
    initializer_list 35 #include <initializer_list> int main() { //deduced to std::initializer_list<int> const auto list = {1,2,2}; for (auto x : list) { // ... } } ne pas oublier! exception : auto + {} initializer_list auto utilise la déduction des templates
  • 46.
    initializer_list 36 void foo(std::initializer_list<int> list) { for(auto i : list) {} } foo({1,2,3}); initializer_list en paramètre
  • 47.
    initializer_list 36 void foo(std::initializer_list<int> list) { for(auto i : list) {} } foo({1,2,3}); passage par valeur initializer_list en paramètre
  • 48.
    initializer_list 37 struct bar { bar(std::initializer_list<int> list); }; constauto b = bar{1,2,3}; can be used to construct objects
  • 49.
    initializer_list 38 void foo(int i,std::initializer_list<int> li) { std::cout << i << " : "; for (auto x : li) std::cout << x << ","; } foo(42, {3,2,3,4}); utilisable avec d’autres arguments
  • 50.
    initializer_list 39 for (auto x: {1,2,3,4}) { std::cout << x << ','; } utilisable dans une boucle simplifiée
  • 51.
    initializer_list 40 #include <iostream> #include <initializer_list> structbar { bar(std::initializer_list<int> list){std::cout << "listn";} bar(int) {std::cout << "intn";} }; int main() { const auto b = bar{1}; } qu’affiche le code suivant?
  • 52.
    initializer_list 41 #include <iostream> #include <vector> intmain () { const auto v1 = std::vector<int>{2}; std::cout << v1.size(); const auto v2 = std::vector<int>(2); std::cout << v2.size(); } mais… qu’affiche le code suivant?
  • 53.
    initializer_list 42 • Permet de ‣faciliter l’écriture des tests unitaires ‣ offrir une API élégante au code client ‣ … const auto v1 = std::vector<int>{1,2,3}; auto v2 = std::vector<int>{}; v2.reserve(3); v2.push_back(1); v2.push_back(2); v2.push_back(3); vs
  • 54.
    initializer_list 42 • Permet de ‣faciliter l’écriture des tests unitaires ‣ offrir une API élégante au code client ‣ … const auto v1 = std::vector<int>{1,2,3}; auto v2 = std::vector<int>{}; v2.reserve(3); v2.push_back(1); v2.push_back(2); v2.push_back(3); vs bonus : plus d’immutabilité
  • 55.
    User-defined literals 43 unsigned longx0 = 0ul; long x1 = 0l; // etc. C++03 : il est possible de suffixer un littéral pour y appliquer des caractéristiques
  • 56.
    User-defined literals 44 long double operator""_deg (long double x) { return x * 3.141592/180; } const auto x = 90.0_deg; pour convertir une valeur
  • 57.
    User-defined literals 44 long double operator""_deg (long double x) { return x * 3.141592/180; } const auto x = 90.0_deg; ne pas oublier “_” pour convertir une valeur
  • 58.
    User-defined literals 45 struct foo { constunsigned long long x_; foo(unsigned long long x) : x_(x) {} }; foo operator"" _foo (unsigned long long n) { return {n}; } const auto f = 123_foo; pour construire un objet
  • 59.
    User-defined literals 46 void operator"" _print(constchar* str, std::size_t) { std::cout << str; } "C++11 rocks!"_print; à partir de chaînes de caractères
  • 60.
    User-defined literals 47 struct point { //??? }; //Yaye! const auto p = 3_x + 5_y; comment obtenir cette construction?
  • 61.
    User-defined literals 48 struct x_type{unsignedlong long value;}; struct y_type{unsigned long long value;}; x_type operator"" _x(unsigned long long value) {return {value};} y_type operator"" _y(unsigned long long value) {return {value};} struct point { unsigned long long x_, y_; private: point(const x_type& x, const y_type& y) : x_(x.value), y_(y.value) {} friend point operator+(const x_type&, const y_type&); }; point operator+(const x_type& x, const y_type& y) {return {x, y};}; // later... const auto p = 3_x + 5_y;
  • 62.
    String literals 49 const autoutf8 = u8"UTF-8 string. u2705n”; std::cout << utf8; const auto utf16 = u8"UTF-16 string.n”; std::wcout << utf16; UTF-8 string. ✅ UTF-16 string. Gestion UTF-8, UTF-16 et UTF-32
  • 63.
    Binary literals 50 const autob = 0b00101010; std::cout << b; // 42 C++14 uniquement
  • 64.
    Séparateur ‘ 51 const autobig_value = 64'000'000'000'000'000; facilite la lecture des grandes constantes const auto b = 0b0010'1010;
  • 65.
    Raw string literals 52 constauto raw = R"(@ tLook Ma, no tabs! some text )"; const auto escaped = "@n" "somen" "textn"; int main() { std::cout << raw; std::cout << "--------------n"; std::cout << escaped; } @ tLook Ma, no tabs! some text -------------- @ some text
  • 66.
    enum class 53 int main() { enume{pink, white, orange, brown}; const auto white = 0ul; } error: redefinition of 'white' as different kind of symbol   const auto white = 0ul;              ^ note: previous definition is here   enum e{pink, white, orange, brown};                ^ enum C++03 : pollution de l’espace de nommage
  • 67.
    enum class 53 int main() { enume{pink, white, orange, brown}; const auto white = 0ul; } ne compile pas error: redefinition of 'white' as different kind of symbol   const auto white = 0ul;              ^ note: previous definition is here   enum e{pink, white, orange, brown};                ^ enum C++03 : pollution de l’espace de nommage
  • 68.
    enum class 54 enum e{pink, white, orange, brown}; if (pink < 99.3) { // ... } enum C++03 : conversion implicite dangereuse
  • 69.
    enum class 54 enum e{pink, white, orange, brown}; if (pink < 99.3) { // ... } enum C++03 : conversion implicite dangereuse compile, mais est-ce vraiment une bonne chose?
  • 70.
    enum class 55 int main() { enumclass e{pink, white, orange, brown}; const auto white = 0ul; const auto e0 = e::white; } enum class créé une nouvelle portée
  • 71.
    enum class 55 int main() { enumclass e{pink, white, orange, brown}; const auto white = 0ul; const auto e0 = e::white; } enum class créé une nouvelle portée accès à la portée de e
  • 72.
    enum class 56 enum classe{pink, white, orange, brown}; // use std::underlying_type<e>::type to find enum’s type type par défaut sous-jacent : int
  • 73.
    enum class 57 enum classe : unsigned long {pink = 0, white, orange, brown = 0xFFFFFFFFFFFFFFFF}; // use std::underlying_type<e>::type to find enum’s type changer le type sous-jacent
  • 74.
    enum class 58 • C++03: il n’y a pas de type par défaut pour énumération ‣ on est donc obligé de la mettre dans les headers ‣ recompilation en cascade s’il faut rajouter un champ • C++11 : on peut faire une pré-déclaration ‣ définition dans une unité de compilation séparée ‣ pas de recompilation en cascade
  • 75.
    nullptr 59 void f(int) {std::cout<< "f(int)";} void f(void*){std::cout << "f(void*)";} f(0); f(NULL); error: call to 'f' is ambiguous   f(NULL);   ^ note: candidate function void f(int)  {std::cout << "f(int)";}      ^ note: candidate function void f(void*){std::cout << "f(void*)";} f(int) f(int) clang/gcc Visual Studio
  • 76.
    nullptr 60 void f(int) {std::cout<< "f(int)";} void f(void*){std::cout << "f(void*)";} f(0); f(nullptr); f(int) f(void*) moins d’ambiguité
  • 77.
    nullptr 61 const auto x= f(); if (x == 0) { // ... } plus de clarté const auto x = f(); if (x == nullptr) { // ... } vs
  • 78.
    nullptr 61 const auto x= f(); if (x == 0) { // ... } plus de clarté const auto x = f(); if (x == nullptr) { // ... } vs intention plus claire
  • 79.
    nullptr 62 type de nullptr voidf(int) {std::cout << "f(int)";} void f(void*) {std::cout << "f(void*)";} void f(std::nullptr_t) {std::cout << "f(nullptr_t)";} f(nullptr);
  • 80.
    alignas 63 alignas(16) int a; alignas(32768)int b; std::cout << &a; std::cout << &b; 0x7fff58d2ffe0 0x7fff58d28000 contrôler l’alignement mémoire
  • 81.
    alignof 64 std::cout << alignof(int); std::cout<< alignof(long); std::cout << alignof(char); std::cout << alignof(char*); connaître l’alignement mémoire (d’un type) 4 8 1 8
  • 82.
    Classes • Délégation deconstructeurs • Héritage des constructeurs des classes de base • Initialisation directe des attributs • Contrôle de l’héritage • Contrôle explicite des méthodes supprimées 65
  • 83.
    Classes 66 délégation de constructeurs(1) struct foo { const int x_; foo(int x) : x_{x} {} foo() : foo{42} {} };
  • 84.
    Classes 66 délégation de constructeurs(1) struct foo { const int x_; foo(int x) : x_{x} {} foo() : foo{42} {} }; délégation
  • 85.
    Classes 67 délégation de constructeurs(2) struct foo { const int x_; foo(int x = 42) : x_{x} {} }; on pourrait utiliser les arguments par défaut problème : dans l’interface de la classe
  • 86.
    Classes 68 héritage des constructeurs(1) : C++03 struct base { const int x_; base(int x) : x_(x) {} }; struct foo : public base { foo(int x) : base(x) {} };
  • 87.
    Classes 68 héritage des constructeurs(1) : C++03 struct base { const int x_; base(int x) : x_(x) {} }; struct foo : public base { foo(int x) : base(x) {} }; appel explicite
  • 88.
    Classes 69 héritage des constructeurs(2) : C++11 struct base { const int x_; base(int x) : x_(x) {} }; struct foo : public base { using base::base; };
  • 89.
    Classes 69 héritage des constructeurs(2) : C++11 struct base { const int x_; base(int x) : x_(x) {} }; struct foo : public base { using base::base; }; hérite de tous les constructeurs de base
  • 90.
    Classes 70 initialisation directe desattributs struct foo { const int x_ = 33; foo(int x) : x_(x) {} foo() {} };
  • 91.
    Classes 70 initialisation directe desattributs struct foo { const int x_ = 33; foo(int x) : x_(x) {} foo() {} }; valeur par défaut utilisée par tous les constructeurs
  • 92.
    Classes 71 contrôle de l’héritageavec final struct foo final {}; struct bar : foo {};
  • 93.
    Classes 71 contrôle de l’héritageavec final struct foo final {}; struct bar : foo {}; ne compile pas error: base 'foo' is marked 'final' struct bar : foo {};              ^
  • 94.
    Classes 72 contrôle de l’héritageavec override (1) struct base { virtual void operator()(float x) const {std::cout << "base " << x;} }; struct derived : public base { virtual void operator()(int x) const {std::cout << "derived " << x;} }; const auto d = derived{}; auto x = 33.3; d(x);
  • 95.
    Classes 72 contrôle de l’héritageavec override (1) struct base { virtual void operator()(float x) const {std::cout << "base " << x;} }; struct derived : public base { virtual void operator()(int x) const {std::cout << "derived " << x;} }; const auto d = derived{}; auto x = 33.3; d(x); affichage?
  • 96.
    Classes 73 contrôle de l’héritageavec override (2) struct base { virtual void operator()(float x) const; }; struct derived : public base { virtual void operator()(int x) const override; };
  • 97.
    Classes 73 contrôle de l’héritageavec override (2) struct base { virtual void operator()(float x) const; }; struct derived : public base { virtual void operator()(int x) const override; }; error: 'operator()' marked 'override' but does not override any member functions   virtual void operator()(int x) const                ^
  • 98.
    Classes 74 contrôle des fonctionsmembres avec default / delete (1) struct non_copyable { non_copyable(){} private: non_copyable(const non_copyable&); non_copyable& operator=(const non_copyable&); }; int main() { const auto nc = non_copyable{}; } error: calling a private constructor of class 'non_copyable'   const auto nc = non_copyable{};                   ^
  • 99.
    Classes 75 struct non_copyable { non_copyable() =default; non_copyable(const non_copyable&) = delete; non_copyable& operator=(const non_copyable&) = delete; }; int main() { const auto nc = non_copyable{}; } error: call to deleted constructor of 'const non_copyable'   const auto nc = non_copyable{};              ^    ~~~~~~~~~~~~~~ note: 'non_copyable' has been explicitly marked deleted here   non_copyable(const non_copyable&) = delete; contrôle des fonctions membres avec default / delete (2)
  • 100.
    Classes 76 struct base { virtual voidoperator()(int x) const; }; struct derived : public base { virtual void operator()(int x) const override; }; int main() { const auto override = 0ul; const auto final = 0ul; } contrôle des fonctions membres avec default / delete (3)
  • 101.
    Classes 76 struct base { virtual voidoperator()(int x) const; }; struct derived : public base { virtual void operator()(int x) const override; }; int main() { const auto override = 0ul; const auto final = 0ul; } pas des mots-clefs contrôle des fonctions membres avec default / delete (3)
  • 102.
    delete 77 void foo(int){}; void foo(long)= delete; void foo(double) = delete; foo(42); // OK foo(42l); // ERROR foo(42.0); // ERROR permet aussi d’empêcher certaines surcharges error: call to deleted function 'foo'   foo(42l);   ^~~ note: candidate function has been explicitly deleted void foo(long) = delete;      ^ note: candidate function void foo(int){};      ^ note: candidate function has been explicitly deleted void foo(double) = delete;      ^
  • 103.
    Rvalues references • Ajoutmajeur au C++ • Travailler explicitement avec les temporaires • Éviter des copies inutiles ‣ move semantics • Perfect forwarding ‣ construire en place ‣ généricité améliorée ‣ … 78
  • 104.
    Rvalues references • lvalues ‣un objet qui occupe une place identifiable en mémoire - qui a une adresse • rvalues ‣ un objet qui n’occupe pas une place identifiable en mémoire - qui n’a pas d’adresse : temporaire 79 lvalue = rvalue lvalue = lvalue rvalue = lvalue int x = 42 int x = y 42 = ...
  • 105.
    Rvalues references 80 int foo(){return 2;} int main() { foo() = 2; } error: lvalue required as left operand of assignment foo() = 2; ^
  • 106.
    Rvalues references 81 struct foo{ foo() { std::cout << “foo()n”; } foo(const foo&) { std::cout << "foo(const foo&)n”; } foo& operator=(const foo&) { std::cout << "operator=(const foo&)n”; return *this; } }; foo bar() {return {};} const auto f0 = foo{}; auto f1 = bar(); f1 = bar(); interlude : copy elision
  • 107.
    Rvalues references 81 struct foo{ foo() { std::cout << “foo()n”; } foo(const foo&) { std::cout << "foo(const foo&)n”; } foo& operator=(const foo&) { std::cout << "operator=(const foo&)n”; return *this; } }; foo bar() {return {};} const auto f0 = foo{}; auto f1 = bar(); f1 = bar(); interlude : copy elision ?
  • 108.
    Rvalues references 82 comment éviterla copie? (1) foo* bar() {return new foo{};} auto f1 = bar(); // later... f1 = bar(); • Retour par pointeur ‣ Coût de l’allocation ‣ Qui est responsable du pointeur?
  • 109.
    Rvalues references 83 comment éviterla copie? (2) void bar(foo*); auto f = foo{}; bar(&f); • Passage par pointeur ou référence en paramètre • Quid des factories?
  • 110.
    Rvalues references 84 comment modifierun temporaire? void bar(foo&); //... auto f = foo{}; bar(f1); bar(foo{}); utile pour un objet fonction avec état (cache, etc.)
  • 111.
    Rvalues references 84 comment modifierun temporaire? void bar(foo&); //... auto f = foo{}; bar(f1); bar(foo{}); ne compile pas utile pour un objet fonction avec état (cache, etc.)
  • 112.
    Rvalues references 85 struct foo{intx;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... bar(foo{42});
  • 113.
    Rvalues references 85 struct foo{intx;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... bar(foo{42}); foo&& : rvalue reference
  • 114.
    Rvalues references 85 struct foo{intx;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... bar(foo{42}); foo&& : rvalue reference foo{42} : rvalue expression
  • 115.
    Rvalues references 85 struct foo{intx;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... bar(foo{42}); foo&& : rvalue reference foo{42} : rvalue expression f : lvalue expression on peut prendre l’adresse de f
  • 116.
    Rvalues references 85 struct foo{intx;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... bar(foo{42}); foo&& : rvalue reference foo{42} : rvalue expression f : lvalue expression on peut prendre l’adresse de f le passage en paramètre “nomme” le temporaire celui-ci devient une lvalue : on peut prendre son adresse sur la pile
  • 117.
    Rvalues references 86 une rvaluereference n’accepte que des rvalue expressions struct foo{int x;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... auto f = foo{42}; bar(f);
  • 118.
    Rvalues references 86 une rvaluereference n’accepte que des rvalue expressions struct foo{int x;}; void bar(foo&& f) { std::cout << f.x << 'n'; } //... auto f = foo{42}; bar(f); erreur : f n’est pas une rvalue expression
  • 119.
    Rvalues references 87 ref-qualifiers struct foo { voidoperator()() const && { std::cout << "&&n"; } void operator()() const & { std::cout << "&n"; } }; //... const auto f = foo{}; f(); foo{}();
  • 120.
    Rvalues references 87 ref-qualifiers struct foo { voidoperator()() const && { std::cout << "&&n"; } void operator()() const & { std::cout << "&n"; } }; //... const auto f = foo{}; f(); foo{}(); en tant que rvalue en tant que lvalue
  • 121.
    Move semantics • Permetd’exprimer le “déplacement” d’une ressource • Permet d’exprimer le changement de propriétaire d’une entité non copiable ‣ mutex, fichier, … • Utilisation des rvalues references • Utilisation de std::move() ‣ instruction au compilateur ‣ ne génère aucun code 88
  • 122.
  • 123.
  • 124.
    Move semantics 90 struct foo{ some_type* ressource; foo& operator=(const foo& rhs) { // destroy this->ressource // clone rhs.ressource // attach this cloned ressource to this->ressource } }; auto f1 = foo{}; auto f2 = foo{}; // later... f2 = f1; copie
  • 125.
    Move semantics 90 struct foo{ some_type* ressource; foo& operator=(const foo& rhs) { // destroy this->ressource // clone rhs.ressource // attach this cloned ressource to this->ressource } }; auto f1 = foo{}; auto f2 = foo{}; // later... f2 = f1; copie O(n)
  • 126.
    Move semantics 91 auto f1= foo{}; //... auto f2 = std::move(f1); // now f1 is “invalid” transfert de ressource (1) move() transforme une lvalue expression en rvalue expression
  • 127.
    Move semantics 92 struct foo{ some_type* ressource; foo& operator=(foo&& rhs) { // destroy this->ressource // copy rhs.ressource pointer to this->ressource } }; auto f1 = foo{}; auto f2 = foo{}; //... f2 = std::move(f1); transfert de ressource (2)
  • 128.
    Move semantics 92 struct foo{ some_type* ressource; foo& operator=(foo&& rhs) { // destroy this->ressource // copy rhs.ressource pointer to this->ressource } }; auto f1 = foo{}; auto f2 = foo{}; //... f2 = std::move(f1); transfert de ressource (2) O(1)
  • 129.
    Move semantics 92 struct foo{ some_type* ressource; foo& operator=(foo&& rhs) { // destroy this->ressource // copy rhs.ressource pointer to this->ressource } }; auto f1 = foo{}; auto f2 = foo{}; //... f2 = std::move(f1); transfert de ressource (2) O(1) mais pas toujours…
  • 130.
    Move semantics 93 struct foo { foo()= default; foo(const foo&) = default; foo(foo&&) = delete; }; int main() { const auto f = foo{}; }
  • 131.
    Move semantics 93 struct foo { foo()= default; foo(const foo&) = default; foo(foo&&) = delete; }; int main() { const auto f = foo{}; } ne compile pas
  • 132.
    Move semantics 94 struct foo{}; foo return_by_value() { const auto f = foo{}; return f; } void take_by_value(foo) {} auto f1 = foo{}; auto f2 = f1; auto f3 = std::move(f1); take_by_value(f2); take_by_value(std::move(f2)); auto f4 = return_by_value(); f1 = return_by_value(); quelles sont les opérations appelées?
  • 133.
    Move semantics 95 transfert depropriété struct foo { some_type ressource; // forbid copy foo(const foo&) = delete; foo& operator=(const foo&) = delete; foo(foo&& rhs) noexcept { // 1. acquire ownership of rhs.ressource // 2. free rhs from its ownership } foo& operator=(foo&&) noexcept { // 1. release ownership of current ressource // 2. acquire ownership of rhs.ressource // 3. free rhs from its ownership } };
  • 134.
    Move semantics 96 struct foo; foobar() { auto f = foo{}; // later... return std::move(f); } std::move en retour d’une fonction
  • 135.
    Move semantics 96 struct foo; foobar() { auto f = foo{}; // later... return std::move(f); } mauvaise idée std::move en retour d’une fonction
  • 136.
    Move semantics 97 const rvaluereferences? void bar(const foo&& f); // what for if we can't move f? void bar(const foo&); // read only réellement utile pour interdire tout usage des rvalues struct foo { foo() {}; foo(foo&&) = delete; foo(const foo&&) = delete; }; // later... const foo f0; foo f1(std::move(f0)); void bar(const foo&& f); // what for if we can't move f? void bar(const foo&); // read only
  • 137.
    Move semantics 98 struct foo; foof1; foo f2; std::swap(f1, f2); C++03 vs C++11 foo() foo() foo(foo&&) operator=(foo&&) operator=(foo&&) foo() foo() foo(const foo&) operator=(const foo&) operator=(const foo&) C++03 C++11
  • 138.
    decltype 99 permet de connaîtrele type d’une expression int* foo(); using ty0 = decltype(foo); print_type<ty0>(); using ty1 = decltype(foo()); print_type<ty1>(); void print_type() [T = int *()] void print_type() [T = int *]
  • 139.
    trailing return type 100 autofoo() -> int { return 42; } std::cout << foo(); type de retour après les paramètres
  • 140.
    trailing return type 101 template<typename T1, typename T2> ??? add(const T1& x, const T2& y) { return x + y; } si le type de retour dépend des paramètres template <typename T1, typename T2> auto add(const T1& x, const T2& y) -> decltype(x+y) { return x + y; }
  • 141.
    trailing return type 102 double(*get_fun2(bool arg))(double) { if (arg) return std::cos; else return std::sin; } plus de lisibilité auto get_fun(bool arg) -> double(*)(double) { if (arg) return std::cos; else return std::sin; } vs
  • 142.
    trailing return type 103 C++14: on peut se passer du type de retour template <typename T1, typename T2> auto add(const T1& x, const T2& y) -> decltype(x+y) { return x + y; }
  • 143.
    trailing return type 104 C++14: on peut se passer du type de retour mais pas toujours… auto get_fun(bool arg) { if (arg) return std::cos; else return std::sin; } error: cannot deduce return type 'auto' from returned value of type '<overloaded function type>'   if (arg) return std::cos;
  • 144.
    105 lambdas struct print1 { void operator()(intx) const { std::cout << x << 'n'; } }; void print2(int x) { std::cout << x << 'n'; } const auto v = {1,2,3}; std::for_each(begin(v), end(v), print1{}); std::for_each(begin(v), end(v), print2); programmation fonctionnelle : objets fonctions et fonctions en argument
  • 145.
    lambdas 106 const auto v= {1,2,3}; std::for_each( begin(v), end(v) , [](int x){std::cout << x << ‘n';} ); const auto v = {1,2,3}; const auto print = [](int x){std::cout << x << 'n';}; std::for_each(begin(v), end(v), print); par l’exemple const auto fun = []{return 42;}; const auto x = fun(); std::cout << x << 'n';
  • 146.
    lambdas 107 const auto print= [](int x){std::cout << x;}; print_type<decltype(print)>(); void print_type() [with T = const main()::<lambda(int)>] type d’un lambda il n’y pas de type pour les lambdas en tant que tel auto est quasi-obligatoire si on veut stocker un lambda ou presque…
  • 147.
    lambdas 108 anatomie d’un lambda [capture](params)-> ret_type {body} comment “capturer” l’environnement corps de la fonction peut-être omis type de retour si nécessaire
  • 148.
    lambdas 109 spécification du typede retour (1) struct foo {}; struct bar : public foo {}; struct baz : public foo {}; const auto foo_maker = [](bool value) { if (value) return new bar{}; else return new baz{}; }; const auto ptr = foo_maker(true); error: return type 'baz *' must match previous return type 'bar *' when lambda expression has unspecified explicit return type                              else       return new baz{};                                         ^
  • 149.
    lambdas 110 spécification du typede retour (2) struct foo {}; struct bar : public foo {}; struct baz : public foo {}; const auto foo_maker = [](bool value) -> foo* { if (value) return new bar{}; else return new baz{}; }; const auto ptr = foo_maker(true);
  • 150.
    lambdas 110 spécification du typede retour (2) struct foo {}; struct bar : public foo {}; struct baz : public foo {}; const auto foo_maker = [](bool value) -> foo* { if (value) return new bar{}; else return new baz{}; }; const auto ptr = foo_maker(true); type de retour
  • 151.
    lambdas 111 capture de l’environnement(1) [] ne capture rien [&] tout par référence [=] tout par valeur [this] capture this [=,&b] copie tout, mais reférence b [&a, b] référence a, copie b environnement = toutes les variables de la portée englobante
  • 152.
    lambdas 111 capture de l’environnement(1) [] ne capture rien [&] tout par référence [=] tout par valeur [this] capture this [=,&b] copie tout, mais reférence b [&a, b] référence a, copie b environnement en “lecture seule” environnement = toutes les variables de la portée englobante const auto x = 42u; []{std::cout << x;}(); // OK auto x = 40u; []{x += 2;}(); // ERROR
  • 153.
    struct foo { int x_; intbar(int y) { return [this](int y) {return x_ + y;} (y); } }; std::cout << foo{40}.bar(2); lambdas 112 auto x = 40u; [&x](int y){x += y;}(2); std::cout << x; auto a = 40u; [&](int b){a += b;}(2); std::cout << x; capture de l’environnement (2)
  • 154.
    struct foo { int x_; intbar(int y) { return [this](int y) {return x_ + y;} (y); } }; std::cout << foo{40}.bar(2); lambdas 112 auto x = 40u; [&x](int y){x += y;}(2); std::cout << x; auto a = 40u; [&](int b){a += b;}(2); std::cout << x; capture de l’environnement (2)
  • 155.
    auto x =44u; [=]{x -= 2;}(); lambdas 113 error: cannot assign to a variable captured by copy in a non-mutable lambda   [=]{x -= 2;}();       ~ ^ auto x = 44u; [=]() mutable {x -= 2;}(); capture de l’environnement (3)
  • 156.
    auto x =44u; [=]{x -= 2;}(); lambdas 113 error: cannot assign to a variable captured by copy in a non-mutable lambda   [=]{x -= 2;}();       ~ ^ auto x = 44u; [=]() mutable {x -= 2;}(); obligatoire capture de l’environnement (3)
  • 157.
    lambdas 114 “fermeture” équivalente àun objet fonction (1) struct closure { double& capture_; int operator()(int x, int& y) const { return (x + y) * capture_; } closure() = delete; closure& operator=(const closure&) = delete; closure(const closure& ) = default; closure(closure&& ) = default; ~closure() = default; }; auto fun0 = [&d](int x, int& y){return (x + y) * d;};
  • 158.
    lambdas 114 “fermeture” équivalente àun objet fonction (1) struct closure { double& capture_; int operator()(int x, int& y) const { return (x + y) * capture_; } closure() = delete; closure& operator=(const closure&) = delete; closure(const closure& ) = default; closure(closure&& ) = default; ~closure() = default; }; auto fun0 = [&d](int x, int& y){return (x + y) * d;}; d’où le mutable
  • 159.
    lambdas 115 auto d =0.0; auto fun1 = fun0; // OK fun1 = fun0; // KO no copy operator using ty = decltype(fun0); auto fun2 = ty{}; // KO no default ctor auto fun0 = [&d](int x, int& y){return (x + y) * d;}; “fermeture” équivalente à un objet fonction (2)
  • 160.
    lambdas 116 en paramètre d’untemplate template <typename F, typename T> void apply(T x) { F{}(x); } auto fun = [](int x){x += 1;}; apply<decltype(fun)>(0);
  • 161.
    lambdas 117 attention aux référencesinvalides auto mk_fun() -> std::function<void (int&)> { auto i = 33u; return [&](int& j){j += i;}; } int main() { auto j = 2; const auto fun = mk_fun(); fun(j); // Doomed to failure }
  • 162.
    lambdas 117 attention aux référencesinvalides auto mk_fun() -> std::function<void (int&)> { auto i = 33u; return [&](int& j){j += i;}; } int main() { auto j = 2; const auto fun = mk_fun(); fun(j); // Doomed to failure } portée locale à mk_fun
  • 163.
    lambdas 117 attention aux référencesinvalides auto mk_fun() -> std::function<void (int&)> { auto i = 33u; return [&](int& j){j += i;}; } int main() { auto j = 2; const auto fun = mk_fun(); fun(j); // Doomed to failure } portée locale à mk_fun lecture de i locale à mk_fun
  • 164.
    lambdas 118 C++14 : paramètresgénériques const auto add = [](const auto& x, const auto& y){return x + y;}; add(1,2); add(std::string{"1"}, std::string{"2"});
  • 165.
    lambdas 119 au service del’immutabilité const auto read_only = [&] { std::vector<int> res; // super-complex initialisation return res }(); read_only.push_back(3); // nope, won’t do auto read_only = std::vector<int>{}; // super-complex initialisation // ...later // damned, still mutable! read_only.push_back(3);
  • 166.
    noexcept • C++03 ‣ ilfaut lister dans la signature d’une fonction les exceptions qu’elle peut lever (throw (…)) ‣ difficile à maintenir ‣ pas ou peu d’aide des compilateurs • C++11 ‣ seule alternative : une fonction peut ou ne peut pas lever d’exception ‣ avantage majeur : le compilateur a plus d’opportunités d’optimisations 120
  • 167.
    noexcept 121 void foo() noexcept { // shallnot throw any exception } struct foo { void operator()() const noexcept { // shall not throw any exception } }; sur fonctions libres ou membres
  • 168.
    noexcept 122 opportunités d’optimisation auto vec= std::vector<foo>{}; const auto f = foo{}; vec.push_back(f); • Si vec doit agrandir son stockage interne ‣ C++03 : recopie - si une exception est lancée pendant la copie, vec original n’est pas modifié (strong guarantee) ‣ C++11 : strong guarantee à tenir - move si foo(foo&&) noexcept - copie sinon
  • 169.
    noexcept 123 noexcept conditionnel (1) voidbar() noexcept { // shall not throw any exception } void foo() noexcept(noexcept(bar())) { bar(); }
  • 170.
    noexcept 124 noexcept conditionnel (2) structbar { void operator()(int) noexcept {} }; struct baz { void operator()(int){} }; template <typename Fun> void foo(int x) noexcept(noexcept(Fun{}(x))) { Fun{}(x); } foo<bar>(33); foo<baz>(42);
  • 171.
    noexcept 125 noexcept appliqué auxlambdas const auto lambda = [](int x) noexcept {return x + 40;}; std::cout << lambda(2);
  • 172.
    • Destructeurs, operatordelete et operator delete[] : noexcept par défaut • C’est une affirmation forte ‣ déclarer une fonction noexcept, puis plus tard enlever cette spécification peut casser le code client • Important pour ‣ foo(foo&&) ‣ foo& operator=(foo&&) ‣ swap(foo&, foo&) ‣ fonctions de calcul (sans allocations dynamiques)? ‣ …? 126 noexcept
  • 173.
    constexpr 127 • const :spécifier des interfaces - foo(const std::vector<int>&); • constexpr : spécifier ce qui est utilisable dans des expressions constantes (et donc possiblement évaluées à la compilation) - tout ce qui est marqué constexpr doit pouvoir produire un résultat constant • Applicable aux objets et aux fonctions Visual Studio 2013
  • 174.
    constexpr 128 auto sz =3; // ERROR: sz's value not known at compilation constexpr auto sz1 = sz; // ERROR: sz's value not known at compilation auto array1 = std::array<int, sz>{}; // OK: 10 is a compile-time constant constexpr auto sz2 = 10; // OK: sz2 is constexpr auto array2 = std::array<int, sz2>{}; objets (1)
  • 175.
    constexpr 129 const auto sz= 3; // OK: sz's value is known at compilation constexpr auto sz1 = sz; // OK: sz's value is known at compilation auto array1 = std::array<int, sz>{}; auto x = 3; const auto sz = x; // ERROR: sz's value not known at compilation constexpr auto sz1 = sz; // ERROR: sz's value not known at compilation auto array1 = std::array<int, sz>{}; objets (2)
  • 176.
    constexpr 130 fonctions constexpr int foo(int a) { returna + 1; } constexpr auto x = foo(42); auto runtime_value = 0u; std::cin >> runtime_value; auto y = foo(runtime_value); fonction expression constante expression non-constante constexpr ✓ ✓ non-constexpr ✕ ✓
  • 177.
    constexpr 131 C++11 seulement une expressionautorisée constexpr int fac(int x) { return x <= 1 ? 1 : x * fac(x-1); } constexpr int fac(int x) { auto res = 1; for (auto i = 1; i <= x; ++i) { res *= i; } return res; } C++14 plusieurs instructions autorisées
  • 178.
    constexpr 132 classes struct foo { int data; constexprfoo(int d) : data(d) {} constexpr int operator()() const {return data + 1;} constexpr operator int() const {return data;} }; auto f0 = foo{42}; constexpr auto f1 = foo{33}; auto i0 = f0(); constexpr auto i1 = f1(); constexpr auto i2 = static_cast<int>(f1); auto i3 = static_cast<int>(f1);
  • 179.
    constexpr 132 classes struct foo { int data; constexprfoo(int d) : data(d) {} constexpr int operator()() const {return data + 1;} constexpr operator int() const {return data;} }; auto f0 = foo{42}; constexpr auto f1 = foo{33}; auto i0 = f0(); constexpr auto i1 = f1(); constexpr auto i2 = static_cast<int>(f1); auto i3 = static_cast<int>(f1); implicite en C++11 à mettre en C++14
  • 180.
    constexpr 133 unsigned int f(unsignedint n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } constexpr unsigned int f(unsigned int n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } en pratique (clang)…
  • 181.
    constexpr 133 unsigned int f(unsignedint n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } constexpr unsigned int f(unsigned int n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } pushq %rbp movq %rsp, %rbp movl $0x78, %eax popq %rbp retq en pratique (clang)…
  • 182.
    constexpr 133 unsigned int f(unsignedint n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } constexpr unsigned int f(unsigned int n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } pushq %rbp movq %rsp, %rbp movl $0x78, %eax popq %rbp retq movdqa %xmm0, %xmm1 movhlps %xmm1, %xmm1 pshufd $0x31, %xmm0, %xmm2 pmuludq %xmm1, %xmm0 pshufd $0x31, %xmm1, %xmm1 pmuludq %xmm2, %xmm1 shufps $-0x78, %xmm1, %xmm0 pshufd $-0x28, %xmm0, %xmm0 pshufd $0x1, %xmm0, %xmm1 pshufd $0x31, %xmm0, %xmm2 pmuludq %xmm0, %xmm2 pmuludq %xmm0, %xmm1 shufps $-0x78, %xmm2, %xmm1 pshufd $-0x28, %xmm1, %xmm0 movd %xmm0, %eax cmpl %edx, %r8d je 0x100000f5d nopw %cs:(%rax,%rax) imull %edi, %eax leal -0x1(%rdi), %ecx cmpl $0x1, %ecx movl %ecx, %edi ja 0x100000f50 popq %rbp retq nop pushq %rbp movq %rsp, %rbp movl $0x78, %eax popq %rbp retq en pratique (clang)…
  • 183.
    constexpr 133 unsigned int f(unsignedint n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } constexpr unsigned int f(unsigned int n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } pushq %rbp movq %rsp, %rbp movl $0x78, %eax popq %rbp retq pushq %rbp movq %rsp, %rbp movl $0x1, %eax cmpl $0x2, %edi jb 0x100000f5d leal -0x1(%rdi), %r8d movl %r8d, %ecx en pratique (clang)…
  • 184.
    constexpr 133 unsigned int f(unsignedint n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } constexpr unsigned int f(unsigned int n) { return n <= 1 ? 1 : n * f(n-1); } int main() { return f(5); } Évaluation à l’exécution Évaluation à la compilation en pratique (clang)…
  • 185.
    static_assert 134 #include <type_traits> template <typenameT> constexpr bool is_power_of_two(T x) { static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value , "Integral and unsigned type expected"); return x and ((x & (x-1)) == 0); } int main() { is_power_of_two(-2); } faire échouer la compilation sur un message clair error: static_assert failed "Integral and unsigned type expected"   static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value   ^              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  • 186.
    static_assert 134 #include <type_traits> template <typenameT> constexpr bool is_power_of_two(T x) { static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value , "Integral and unsigned type expected"); return x and ((x & (x-1)) == 0); } int main() { is_power_of_two(-2); } faire échouer la compilation sur un message clair error: static_assert failed "Integral and unsigned type expected"   static_assert( std::is_integral<T>::value and std::is_unsigned<T>::value   ^              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ expression constante requise
  • 187.
  • 188.
    Généricité 136 struct foo_vector { foo* data; unsignedint sz; void push_back(const foo& x) { data[sz++] = x; } }; une nécessité pour les structures de données… auto int_vec = int_vector{}; auto foo_vec = foo_vector{}; int_vec.push_back(1); foo_vec.push_back(foo{}); struct int_vector { int* data; unsigned int sz; void push_back(const int& x) { data[sz++] = x; } };
  • 189.
    Généricité 137 struct int_vector { int* data; unsignedint sz; void push_back(const int& x) { data[sz++] = x; } }; une nécessité pour les structures de données… struct foo_vector { foo* data; unsigned int sz; void push_back(const foo& x) { data[sz++] = x; } };
  • 190.
    Généricité 138 template <typename T> structvector { T* data; unsigned int sz; void push_back(const T& x) { data[sz++] = x; } }; une nécessité pour les structures de données… auto int_vec = vector<int>{}; auto foo_vec = vector<foo>{}; int_vec.push_back(1); foo_vec.push_back(foo{});
  • 191.
    Généricité 138 template <typename T> structvector { T* data; unsigned int sz; void push_back(const T& x) { data[sz++] = x; } }; une nécessité pour les structures de données… auto int_vec = vector<int>{}; auto foo_vec = vector<foo>{}; int_vec.push_back(1); foo_vec.push_back(foo{}); “instanciation” du type générique vector avec un entier
  • 192.
    Généricité 139 int sum(const std::vector<int>&vec) { auto acc = int{}; for (const auto& x : vec) acc = acc + x; return acc; } une nécessité pour les algorithmes… const auto vec0 = std::vector<int>{1,2,3}; const auto s0 = sum(vec0); const auto vec1 = std::vector<foo>{1,2,3}; const auto s1 = sum(vec1); struct foo; foo operator+(const foo&, const foo&); foo sum(const std::vector<foo>& vec) { auto acc = foo{}; for (const auto& x : vec) acc = acc + x; return acc; }
  • 193.
    Généricité 140 int sum(const std::vector<int>&vec) { auto acc = int{}; for (const auto& x : vec) acc = acc + x; return acc; } une nécessité pour les algorithmes… foo sum(const std::vector<foo>& vec) { auto acc = foo{}; for (const auto& x : vec) acc = acc + x; return acc; }
  • 194.
    Généricité 141 template <typename T> Tsum(const std::vector<T>& vec) { auto acc = T{}; for (const auto& x : vec) { acc = acc + x; } return acc; } une nécessité pour les algorithmes… const auto vec0 = std::vector<int>{1,2,3}; const auto s0 = sum(vec0); const auto vec1 = std::vector<foo>{1,2,3}; const auto s1 = sum(vec1);
  • 195.
    Généricité 141 template <typename T> Tsum(const std::vector<T>& vec) { auto acc = T{}; for (const auto& x : vec) { acc = acc + x; } return acc; } rien n’a changé! une nécessité pour les algorithmes… const auto vec0 = std::vector<int>{1,2,3}; const auto s0 = sum(vec0); const auto vec1 = std::vector<foo>{1,2,3}; const auto s1 = sum(vec1);
  • 196.
    Structures templates 142 template <typenameT> struct foo { T x_; foo(const T& x) : x_(x) {} const T& x() const {return x_;} T& x() {return x_;} }; auto f_int = foo<int>{42}; f_int.x() += 2; auto f_char = foo<char>{'a'}; f_char.x() = 'b';
  • 197.
    Structures templates 142 template <typenameT> struct foo { T x_; foo(const T& x) : x_(x) {} const T& x() const {return x_;} T& x() {return x_;} }; paramètre générique : c’est un type, pas une valeur auto f_int = foo<int>{42}; f_int.x() += 2; auto f_char = foo<char>{'a'}; f_char.x() = 'b';
  • 198.
    Structures templates 142 template <typenameT> struct foo { T x_; foo(const T& x) : x_(x) {} const T& x() const {return x_;} T& x() {return x_;} }; paramètre générique : c’est un type, pas une valeur accès au type dans toute la classe auto f_int = foo<int>{42}; f_int.x() += 2; auto f_char = foo<char>{'a'}; f_char.x() = 'b';
  • 199.
    Fonctions templates 143 struct foo{}; template<typename T, typename U> void fun0(const T&, const U&); template <typename T, typename U> void fun1(const T&); fun0(0, foo{}); fun1<int, foo>(0); déduction automatique à partir des arguments
  • 200.
    Fonctions templates 143 struct foo{}; template<typename T, typename U> void fun0(const T&, const U&); template <typename T, typename U> void fun1(const T&); fun0(0, foo{}); fun1<int, foo>(0); déduction automatique à partir des arguments tous les types sont présents dans les arguments
  • 201.
    Fonctions templates 143 struct foo{}; template<typename T, typename U> void fun0(const T&, const U&); template <typename T, typename U> void fun1(const T&); fun0(0, foo{}); fun1<int, foo>(0); déduction automatique à partir des arguments tous les types sont présents dans les arguments les types ne sont pas tous présents dans les arguments, il donc les expliciter
  • 202.
    Fonctions templates 144 template <typenameT> struct foo { T x; }; template <typename T> foo<T> mk_foo(const T& x) { return {x}; } const auto f0 = foo<int>(42); const auto f1 = mk_foo(42); pratique pour “cacher” l’argument du template
  • 203.
    Fonctions templates 144 template <typenameT> struct foo { T x; }; template <typename T> foo<T> mk_foo(const T& x) { return {x}; } const auto f0 = foo<int>(42); const auto f1 = mk_foo(42); pratique pour “cacher” l’argument du template pas de type explicite
  • 204.
    Fonctions templates 145 les fonctionsmembres peuvent être aussi génériques template <typename T> struct foo { T x_; foo(const T& x) : x_(x) {} template <typename U> void bar(const U&) {} }; auto f = foo<int>{42}; f.bar(3.22);
  • 205.
    Fonctions templates 145 les fonctionsmembres peuvent être aussi génériques template <typename T> struct foo { T x_; foo(const T& x) : x_(x) {} template <typename U> void bar(const U&) {} }; auto f = foo<int>{42}; f.bar(3.22);méthode générique
  • 206.
    Templates :“fabriques” 146 • Unestructure template est une “fabrique de type” ‣ template <typename T> struct foo n’est pas un type - on ne peut pas créer d’objet de type foo<T> ‣ foo<int> est “généré” à partir de foo<T> - c’est un type, on peut créer un objet à partir de foo<int> • Une fonction template est une “fabrique de fonctions” ‣ on n’appelle pas directement template <typename T> foo()
  • 207.
    Accès à untype imbriqué 147 struct bar { typedef int value_type; }; void foo(const bar&) { bar::value_type x; } dans un contexte non-générique
  • 208.
    Accès à untype imbriqué 147 struct bar { typedef int value_type; }; void foo(const bar&) { bar::value_type x; } dans un contexte non-générique OK
  • 209.
    Accès à untype imbriqué 148 struct bar { typedef int value_type; }; template <typename T> void foo(const T&) { T::value_type x; } error: missing 'typename' prior to dependent type name 'T::value_type'   T::value_type x;   ^~~~~~~~~~~~~   typename dans un contexte générique
  • 210.
    Accès à untype imbriqué 148 struct bar { typedef int value_type; }; template <typename T> void foo(const T&) { T::value_type x; } error: missing 'typename' prior to dependent type name 'T::value_type'   T::value_type x;   ^~~~~~~~~~~~~   typename ! dans un contexte générique
  • 211.
    Accès à untype imbriqué 149 struct bar { typedef int value_type; }; template <typename T> void foo(const T&) { typename T::value_type x; } dans un contexte générique
  • 212.
    Accès à untype imbriqué 149 struct bar { typedef int value_type; }; template <typename T> void foo(const T&) { typename T::value_type x; } dans un contexte générique OK
  • 213.
    150 Dependant templates struct bar { template<typename U> U get() const {} }; template <typename T> void foo(const T& b) { const auto x = b.get<int>(); } auto b = bar{}; foo(b); error: use 'template' keyword to treat 'get' as a dependent template name   const auto x = b.get<T>();                    ^                    template
  • 214.
    150 Dependant templates struct bar { template<typename U> U get() const {} }; template <typename T> void foo(const T& b) { const auto x = b.get<int>(); } auto b = bar{}; foo(b); error: use 'template' keyword to treat 'get' as a dependent template name   const auto x = b.get<T>();                    ^                    template !
  • 215.
    151 Dependant templates struct bar { template<typename U> U get() const {} }; template <typename T> void foo(const T& b) { const auto x = b.template get<int>(); }
  • 216.
    151 Dependant templates struct bar { template<typename U> U get() const {} }; template <typename T> void foo(const T& b) { const auto x = b.template get<int>(); } OK
  • 217.
    151 Dependant templates struct bar { template<typename U> U get() const {} }; template <typename T> void foo(const T& b) { const auto x = b.template get<int>(); } OK get est une fonction template qui dépend du type T qui est lui même un paramètre template
  • 218.
    alias 152 // the oldway typedef int my_typedef; // aliases using my_alias = int; using : successeur de typedef
  • 219.
    alias 152 // the oldway typedef int my_typedef; // aliases using my_alias = int; lecture homogène avec auto var = …; using : successeur de typedef
  • 220.
    alias 153 template <typename T,typename U> struct foo{}; template <typename T> struct templated_typedef { typedef foo<T, int> type; }; int main() { const auto f = templated_typedef<int>::type{}; } essayons tout de même (1) impossible d’avoir des typedef template
  • 221.
    alias 154 template <typename T,typename U> struct foo{}; template <typename T> struct templated_typedef { typedef foo<T, int> type; }; template <typename T> void bar(const typename templated_typedef<T>::type&); int main() { const auto f = templated_typedef<int>::type{}; bar(f); } impossible d’avoir des typedef template essayons tout de même (2)
  • 222.
    alias 154 template <typename T,typename U> struct foo{}; template <typename T> struct templated_typedef { typedef foo<T, int> type; }; template <typename T> void bar(const typename templated_typedef<T>::type&); int main() { const auto f = templated_typedef<int>::type{}; bar(f); } note: candidate template ignored: couldn't infer template argument 'T' void bar(const typename templated_typedef<T>::type&); impossible d’avoir des typedef template essayons tout de même (2) !
  • 223.
    alias 155 template <typename T,typename U> struct foo{}; template <typename T> using alias_t = foo<T, int>; int main() { const auto f0 = foo<double, int>{}; const auto f1 = alias_t<int>{}; } alias templates
  • 224.
    Règles de déductions 156 template<typename T> void f(ParamType param); f(expression); deux types à déduire template <typename T> void f(const T& param); int x = 42; f(x); pseudo-code :
  • 225.
    Règles de déductions 156 template<typename T> void f(ParamType param); f(expression); type deT deux types à déduire template <typename T> void f(const T& param); int x = 42; f(x); pseudo-code :
  • 226.
    Règles de déductions 156 template<typename T> void f(ParamType param); f(expression); type deT deux types à déduire type de param template <typename T> void f(const T& param); int x = 42; f(x); pseudo-code :
  • 227.
    Règles de déductions 156 template<typename T> void f(ParamType param); f(expression); type deT deux types à déduire type de param template <typename T> void f(const T& param); int x = 42; f(x); int pseudo-code :
  • 228.
    Règles de déductions 156 template<typename T> void f(ParamType param); f(expression); type deT deux types à déduire type de param template <typename T> void f(const T& param); int x = 42; f(x); int const int& pseudo-code :
  • 229.
    Règles de déductions •Trois cas, le type de param ‣ est un pointeur ou une référence ‣ n’est ni un pointeur ni une référence ‣ est une référence universelle 157 template <typename T> void f(ParamType param);
  • 230.
    Règles de déductions 158 ParamType: pointeur ou référence (1) template <typename T> void f(T& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int int& f(cx) const int const int& f(rx) const int const int&
  • 231.
    Règles de déductions 158 ParamType: pointeur ou référence (1) template <typename T> void f(T& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int int& f(cx) const int const int& f(rx) const int const int& idem pour les pointeurs
  • 232.
    Règles de déductions 159 ParamType: pointeur ou référence (2) template <typename T> void f(const T& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int const int& f(cx) const int const int& f(rx) const int const int&
  • 233.
    Règles de déductions 159 ParamType: pointeur ou référence (2) template <typename T> void f(const T& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int const int& f(cx) const int const int& f(rx) const int const int& idem pour les pointeurs
  • 234.
    Règles de déductions 160 ParamType: ni pointeur, ni référence template <typename T> void f(T param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int int f(cx) int int f(rx) int int
  • 235.
    Règles de déductions 160 ParamType: ni pointeur, ni référence template <typename T> void f(T param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int int f(cx) int int f(rx) int int par valeur
  • 236.
    Règles de déductions 160 ParamType: ni pointeur, ni référence template <typename T> void f(T param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int int f(cx) int int f(rx) int int par valeur copie : const est ignoré
  • 237.
    Règles de déductions 161 ParamType: rvalue reference template <typename T> void f(T&& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int& int& f(cx) const int& const int& f(rx) const int& const int& f(42) int int&& lvalues rvalues
  • 238.
    Règles de déductions 161 ParamType: rvalue reference template <typename T> void f(T&& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int& int& f(cx) const int& const int& f(rx) const int& const int& f(42) int int&& idem pour les pointeurs lvalues rvalues
  • 239.
    Règles de déductions 161 ParamType: rvalue reference template <typename T> void f(T&& param); int x = 2; const int cx = x; const int& rx = x; T param f(x) int& int& f(cx) const int& const int& f(rx) const int& const int& f(42) int int&& idem pour les pointeurs lvalues rvalues “référence universelle”
  • 240.
    162 template <typename T> voidprint_type() { std::cout << __PRETTY_FUNCTION__; } Afficher le type déduit void print_type() [T = foo] (GCC, clang)
  • 241.
    163 template <typename T> voidprint_type() { std::cout << __FUNCSIG__; } void _cdecl print_type<struct foo>(void) (Visual Studio 2013) Afficher le type déduit
  • 242.
    164 template <typename T> structprint_type; print_type<foo>{}; (tous compilateurs) Afficher le type déduit error: implicit instantiation of undefined template 'print_type<foo>'   print_type<foo>{};   ^
  • 243.
    164 template <typename T> structprint_type; print_type<foo>{}; (tous compilateurs) Afficher le type déduit ne pas donner de définition error: implicit instantiation of undefined template 'print_type<foo>'   print_type<foo>{};   ^
  • 244.
    Spécialisation 165 scénario : unalgorithme peut-être optimisé pour un type particulier struct foo{}; template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <> struct algo<foo> { void operator()() {std::cout << "O(1)";} }; algo<foo>{}(); // “O(1)” algo<int>{}(); // “O(n)”
  • 245.
    Spécialisation 165 scénario : unalgorithme peut-être optimisé pour un type particulier struct foo{}; template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <> struct algo<foo> { void operator()() {std::cout << "O(1)";} }; algo<foo>{}(); // “O(1)” algo<int>{}(); // “O(n)” générique
  • 246.
    Spécialisation 165 scénario : unalgorithme peut-être optimisé pour un type particulier struct foo{}; template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <> struct algo<foo> { void operator()() {std::cout << "O(1)";} }; algo<foo>{}(); // “O(1)” algo<int>{}(); // “O(n)” spécialisation générique
  • 247.
    Spécialisation 166 fonctions struct foo{}; template <typenameT> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } algo(foo{}); // “O(1)” algo(42); // “O(n)”
  • 248.
    Spécialisation 166 fonctions struct foo{}; template <typenameT> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } algo(foo{}); // “O(1)” algo(42); // “O(n)” générique
  • 249.
    Spécialisation 166 fonctions struct foo{}; template <typenameT> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } algo(foo{}); // “O(1)” algo(42); // “O(n)” spécialisation générique
  • 250.
    Spécialisation 167 fonctions, une petitesurprise… template <typename T> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } void algo(const foo&) { std::cout << "O(1) bis"; } algo(foo{}); // “O(1) bis” algo(42); // “O(n)”
  • 251.
    Spécialisation 167 fonctions, une petitesurprise… template <typename T> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } void algo(const foo&) { std::cout << "O(1) bis"; } algo(foo{}); // “O(1) bis” algo(42); // “O(n)” spécialisation générique surcharge
  • 252.
    Spécialisation 167 fonctions, une petitesurprise… template <typename T> void algo(const T&) { std::cout << "O(n)"; } template <> void algo(const foo&) { std::cout << "O(1)"; } void algo(const foo&) { std::cout << "O(1) bis"; } algo(foo{}); // “O(1) bis” algo(42); // “O(n)” spécialisation générique surcharge préférer les surcharges aux spécialisations
  • 253.
    Spécialisation partielle 168 scénario :un algorithme peut-être optimisé pour un conteneur générique particulier template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)”
  • 254.
    Spécialisation partielle 168 scénario :un algorithme peut-être optimisé pour un conteneur générique particulier template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” générique
  • 255.
    Spécialisation partielle 168 scénario :un algorithme peut-être optimisé pour un conteneur générique particulier template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” générique spécialisation partielle
  • 256.
    Spécialisation partielle 169 on peutgarder des spécialisations totales template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; template <> struct algo<std::vector<double>> { void operator()() {std::cout << "O(1) double";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” algo<std::vector<double>>{}(); // “O(1) double”
  • 257.
    Spécialisation partielle 169 on peutgarder des spécialisations totales template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; template <> struct algo<std::vector<double>> { void operator()() {std::cout << "O(1) double";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” algo<std::vector<double>>{}(); // “O(1) double” générique
  • 258.
    Spécialisation partielle 169 on peutgarder des spécialisations totales template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; template <> struct algo<std::vector<double>> { void operator()() {std::cout << "O(1) double";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” algo<std::vector<double>>{}(); // “O(1) double” générique spécialisation partielle
  • 259.
    Spécialisation partielle 169 on peutgarder des spécialisations totales template <typename T> struct algo { void operator()() {std::cout << "O(n)";} }; template <typename T> struct algo<std::vector<T>> { void operator()() {std::cout << "O(1)";} }; template <> struct algo<std::vector<double>> { void operator()() {std::cout << "O(1) double";} }; algo<int>{}(); // “O(n)” algo<std::vector<int>>{}(); // “O(1)” algo<std::vector<double>>{}(); // “O(1) double” générique spécialisation partielle spécialisation totale
  • 260.
    Template-template arguments 170 template <typenameT> struct vector {}; template <typename T, template <typename DUMMY> class Container> struct foo { Container<T> cont_; }; foo<int, vector>{}; prendre en paramètre template un template (1)
  • 261.
    Template-template arguments 170 template <typenameT> struct vector {}; template <typename T, template <typename DUMMY> class Container> struct foo { Container<T> cont_; }; foo<int, vector>{}; obligatoire prendre en paramètre template un template (1)
  • 262.
    Template-template arguments 170 template <typenameT> struct vector {}; template <typename T, template <typename DUMMY> class Container> struct foo { Container<T> cont_; }; foo<int, vector>{}; obligatoire nom sans importance peut être omis prendre en paramètre template un template (1)
  • 263.
    Template-template arguments 170 template <typenameT> struct vector {}; template <typename T, template <typename DUMMY> class Container> struct foo { Container<T> cont_; }; foo<int, vector>{}; obligatoire nom sans importance peut être omis T est passé à Container prendre en paramètre template un template (1)
  • 264.
    Template-template arguments 171 template <typenameT, template <typename> class Container> struct foo { Container<T> cont_; }; foo<int, std::vector>{}; prendre en paramètre template un template (2) error: template template argument has different template parameters than its corresponding template template parameter   foo<int, std::vector>{};                 ^ note: too many template parameters in template template argument template <class _Tp, class _Allocator = allocator<_Tp> > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ attention aux nombres de paramètres…
  • 265.
    Template-template arguments 171 template <typenameT, template <typename> class Container> struct foo { Container<T> cont_; }; foo<int, std::vector>{}; prendre en paramètre template un template (2) ! error: template template argument has different template parameters than its corresponding template template parameter   foo<int, std::vector>{};                 ^ note: too many template parameters in template template argument template <class _Tp, class _Allocator = allocator<_Tp> > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ attention aux nombres de paramètres…
  • 266.
    Valeurs en paramètrestemplate 172 template <int Value> struct foo { void operator()() const { std::cout << Value; } }; foo<42>{}(); on peut utiliser des valeurs comme paramètres template template <bool Value> struct foo { void operator()() const { std::cout << Value; } }; foo<true>{}();
  • 267.
    Valeurs en paramètrestemplate 173 expressions constantes seulement template <int Value> struct foo; const auto i = 42; auto j = i; foo<i>{}(); // OK foo<j>{}(); // ERROR error: non-type template argument is not a constant expression   foo<j>{}(); // ERROR       ^ note: read of non-const variable 'j' is not allowed in a constant expression
  • 268.
    Valeurs en paramètrestemplate 173 expressions constantes seulement template <int Value> struct foo; const auto i = 42; auto j = i; foo<i>{}(); // OK foo<j>{}(); // ERROR ! error: non-type template argument is not a constant expression   foo<j>{}(); // ERROR       ^ note: read of non-const variable 'j' is not allowed in a constant expression
  • 269.
    Valeurs en paramètrestemplate 174 la spécialisation fonctionne aussi template <int Value> struct fact { static const auto value = Value * fact<Value - 1>::value; }; template <> struct fact<0> { static const auto value = 1; }; std::cout << fact<5>::value; // 120
  • 270.
    Valeurs en paramètrestemplate 174 la spécialisation fonctionne aussi template <int Value> struct fact { static const auto value = Value * fact<Value - 1>::value; }; template <> struct fact<0> { static const auto value = 1; }; std::cout << fact<5>::value; // 120 récursion : cas général récursion : cas de base
  • 271.
    Curiously RecurringTemplate Pattern(CRTP) 175 template <typename T> struct foo {}; struct bar : public foo<bar> {}; classe héritée générique
  • 272.
    Variadic templates 176 template<typename T> Tadd(T x) { return x; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } add(1,2); // 3 add(1,2,3); // 6 add(1,2,3,4); // 10 // etc. nombre variable de paramètres template
  • 273.
    Variadic templates 176 template<typename T> Tadd(T x) { return x; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } add(1,2); // 3 add(1,2,3); // 6 add(1,2,3,4); // 10 // etc. nombre variable de paramètres template “parameter pack”
  • 274.
    Variadic templates 176 template<typename T> Tadd(T x) { return x; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } add(1,2); // 3 add(1,2,3); // 6 add(1,2,3,4); // 10 // etc. nombre variable de paramètres template expansion du pack “parameter pack”
  • 275.
    Variadic templates 177 int add(intarg1) { return arg1; } int add(int arg1, int arg2) { return arg1 + add(arg2); } int add(int arg1, int arg2, int arg3) { return arg1 + add(arg2, arg3); } add(1,2,3); // 6 code “déroulé” template<typename T> T add(T x) { return x; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); }
  • 276.
    Variadic templates 178 raisonnement programmationfonctionnelle pure template<typename T> T add(T x) { return v; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } “déconstruction" de la liste de types head::tail Caml, Standard ML, Haskell, Scala, etc.
  • 277.
    Variadic templates 178 raisonnement programmationfonctionnelle pure template<typename T> T add(T x) { return v; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } head “déconstruction" de la liste de types head::tail Caml, Standard ML, Haskell, Scala, etc.
  • 278.
    Variadic templates 178 raisonnement programmationfonctionnelle pure template<typename T> T add(T x) { return v; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } head tail “déconstruction" de la liste de types head::tail Caml, Standard ML, Haskell, Scala, etc.
  • 279.
    Variadic templates 178 raisonnement programmationfonctionnelle pure template<typename T> T add(T x) { return v; } template<typename T, typename... Ts> T add(T x, Ts... xs) { return x + add(xs...); } head tail “déconstruction" de la liste de types head::tail Caml, Standard ML, Haskell, Scala, etc. head
  • 280.
    Variadic templates 179 template<typename T> Tadd(T v) { return v; } template<typename T, typename... Args> T add(T first, Args... args) { return first + add(args...); } std::string operator ""_s(const char *str, std::size_t) { return {str}; } add(“1”_s,"2"_s,"3"_s); // “123” pour le plaisir…
  • 281.
    Variadic templates 180 // Signatureof a meta-function that add integers template <int... Xs> struct add; template <int X, int... Xs> struct add<X, Xs...> { static const auto value = X + add<Xs...>::value; }; template <int X> struct add<X> { static const auto value = X ; }; add<1,2,3>::value; // 6 les struct/class aussi
  • 282.
    Variadic templates 181 connaître lataille d’un pack avec sizeof... template <typename... Ts> std::size_t foo() { return sizeof...(Ts); } std::cout << foo<int, double, char>(); // 3
  • 283.
    Variadic templates 182 expansion d’unpack (1) template <typename... Ts> struct foo{}; template<typename X, typename Y, typename... Z> void bar(X, Y, Z...) { print_type< foo<X, Y, Z...> >(); print_type< foo<X, Z..., Y> >(); print_type< foo<Z..., X, Y> >(); } bar(int{}, int{}, double{}, std::string{}); void print_type() [T = foo<int, int, double, std::__1::basic_string<char> >] void print_type() [T = foo<int, double, std::__1::basic_string<char>, int>] void print_type() [T = foo<double, std::__1::basic_string<char>, int, int>]
  • 284.
    Variadic templates 182 expansion d’unpack (1) template <typename... Ts> struct foo{}; template<typename X, typename Y, typename... Z> void bar(X, Y, Z...) { print_type< foo<X, Y, Z...> >(); print_type< foo<X, Z..., Y> >(); print_type< foo<Z..., X, Y> >(); } bar(int{}, int{}, double{}, std::string{}); void print_type() [T = foo<int, int, double, std::__1::basic_string<char> >] void print_type() [T = foo<int, double, std::__1::basic_string<char>, int>] void print_type() [T = foo<double, std::__1::basic_string<char>, int, int>] Z… en dernière position
  • 285.
    Variadic templates 183 expansion d’unpack (2) template<typename... Values> std::array<int, sizeof...(Values)> make_array(Values... values) { return std::array<int, sizeof...(Values)>{values...}; } const auto a = make_array(1,2,3); template<typename... Args> void foo(Args... values) { int tab[sizeof...(Args)] = {values...}; } foo(1,2,3);
  • 286.
    Variadic templates 184 expansion d’unpack : héritage struct base1 { void foo(){} }; struct base2 { void bar(){} }; template <typename... Bases> class derived : public Bases... {}; auto d = derived<base1, base2>{}; d.foo(); d.bar();
  • 287.
    Variadic templates 185 spécialisation template <typename...Ts> struct foo {}; template <typename... Ts> struct bar; template <typename... Ts> struct bar<foo<Ts...>> {}; using foo_type = foo<int, char>; using bar_type = bar<foo_type>;
  • 288.
    Variadic templates 185 spécialisation template <typename...Ts> struct foo {}; template <typename... Ts> struct bar; template <typename... Ts> struct bar<foo<Ts...>> {}; using foo_type = foo<int, char>; using bar_type = bar<foo_type>; permet d’accéder à liste de paramètres de foo
  • 289.
    Variadic templates 186 template <typename...Ts> struct tuple {}; template <typename T, typename... Ts> struct tuple<T, Ts...> : tuple<Ts...> { tuple(T t, Ts... ts) : tuple<Ts...>(ts...) , current(t) {} T current; }; structure récursive
  • 290.
    Références universelles 187 template <typenameT> void f(T&& param); • Dénomination de Scott Meyers • En présence d’une déduction de type (templates) • Il faudrait parler de ‣ forwarding reference ‣ en cours de standardisation (la dénomination) ‣ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/ n4164.pdf
  • 291.
    Références universelles 187 template <typenameT> void f(T&& param); • Dénomination de Scott Meyers • En présence d’une déduction de type (templates) • Il faudrait parler de ‣ forwarding reference ‣ en cours de standardisation (la dénomination) ‣ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/ n4164.pdf && sur paramètre template
  • 292.
    Perfect forwarding 188 le problème: passer des paramètres de manière transparente à une sous-fonction template <typename Fun, typename T> void logger(const T& x) { std::cout << "beforen"; Fun{}(x); std::cout << "aftern"; } template <typename Fun, typename T> void logger(T& x) { std::cout << "beforen"; Fun{}(x); std::cout << "aftern"; } et s’il y a plusieurs paramètres?…
  • 293.
    Perfect forwarding solution :std::forward template <typename Fun, typename Arg> void logger(Arg&& x) { std::cout << "beforen"; Fun{}(std::forward<Arg>(x)); std::cout << "aftern"; }
  • 294.
    Perfect forwarding 190 comment çafonctionne? (1) A& & → A& A& && → A& A&& & → A& A&& && → A&& si invocation f(a) a de type A Arg x lvalue A& A& rvalue A&& A&& template<typename Arg> void f(Arg&& x); “collapsing rules” application des collapsing rules
  • 295.
    Perfect forwarding 191 comment çafonctionne? (2) template<class T> T&& forward(typename remove_reference<T>::type& a) { return static_cast<T&&>(a); }
  • 296.
    Perfect forwarding 192 void logger(X&&& x) { std::cout << "beforen"; Fun{}(std::forward<X&>(x)); std::cout << "aftern"; } X& && forward(typename remove_reference<X&>::type& a) { return static_cast<X& &&>(a); } void logger(X& x) { std::cout << "beforen"; Fun{}(std::forward<X&>(x)); std::cout << "aftern"; } X& forward(X& a) { return static_cast<X&>(a); } sur une lvalue
  • 297.
    Perfect forwarding 193 sur unervalue void logger(X&& && x) { std::cout << "beforen"; Fun{}(std::forward<X&&>(x)); std::cout << "aftern"; } X&& && forward(typename remove_reference<X&&>::type& a) { return static_cast<X&& &&>(a); } void logger(X&& x) { std::cout << "beforen"; Fun{}(std::forward<X&&>(x)); std::cout << "aftern"; } X&& forward(X& a) { return static_cast<X&&>(a); }
  • 298.
    Perfect forwarding vsmove 194 void foo(int&&) {std::cout << "int&&";} void foo(const int&) {std::cout << "const int&";} void foo(int&) {std::cout << "int&";} template <typename T> void apply(T&& x) { std::cout << "A"; foo(std::forward<T>(x));} } void apply(int&& x) { std::cout << "B"; foo(std::move(x)); } apply(i); apply(ri); apply(cri); apply(42); apply(std::move(i)); quel est l’affichage? auto i = 2; auto& ri = i; const auto& cri = i;
  • 299.
  • 300.
    196 begin/end auto vec =std::vector<int>{1,2,3}; auto it = begin(vec); // vec.begin() *it = 42; auto cit = cbegin(vec); // vec.cbegin() *cit = 33; // error fonctions libres for (auto cit = cbegin(vec); cit != cend(vec); ++cit) { // ... }
  • 301.
    197 begin/end tableaux C int tab[]= {1,2,3}; for (auto it = std::begin(tab); it != std::end(tab); ++it) { *it = 0; }
  • 302.
    197 begin/end tableaux C int tab[]= {1,2,3}; for (auto it = std::begin(tab); it != std::end(tab); ++it) { *it = 0; } appel “fully qualified”
  • 303.
    198 next/prev auto vec =std::vector<int>{1,2,3}; auto it = begin(vec); auto it1 = next(it); // return a copy incremented by 1 auto it2 = next(it, 2); // return a copy incremented by 2 auto it3 = prev(it2); // return a copy decremented by 1 auto it4 = prev(it3, 1); // return a copy decremented by 1
  • 304.
    199 move déplacer un conteneur autovec1 = std::vector<foo>{foo{}, foo{}}; auto vec2 = std::vector<foo>{}; std::cout << vec1.size(); std::cout << vec2.size(); std::move(begin(vec1), end(vec1), std::back_inserter(vec2)); std::cout << vec1.size(); std::cout << vec2.size();
  • 305.
    200 all_of, any_of, any_of autovec1 = std::vector<bool>{true, true, true}; auto vec2 = std::vector<bool>{true, false, true}; std::cout << std::boolalpha; std::cout << std::all_of( begin(vec1), end(vec1) , [](bool x){return x;}); std::cout << std::any_of( begin(vec2), end(vec2) , [](bool x){return not x;});
  • 306.
    201 emplace, emplace_back construction enplace grâce au perfect forwarding struct foo { foo(int) {std::cout << "foo(int)";} foo(const foo&) {std::cout << "foo(const foo&)";} foo& operator=(const foo&) {std::cout << "operator=(const foo&)"; return *this;} foo(foo&&) {std::cout << "foo(foo&&)";} foo& operator=(foo&&) {std::cout << "operator=(foo&&)"; return *this;} }; auto vec1 = std::vector<foo>{}; vec1.reserve(3); auto f = foo{1}; vec1.push_back(f); vec1.push_back(foo{2}); vec1.emplace_back(3);
  • 307.
    202 shrink_to_fit deque, string etvector auto vec1 = std::vector<int>{}; std::cout << vec1.capacity(); vec1.push_back(1); vec1.push_back(1); vec1.push_back(1); std::cout << vec1.capacity(); vec1.shrink_to_fit(); std::cout << vec1.capacity(); peut libérer de la mémoire
  • 308.
    203 forward_list liste simplement chaînée autol = std::forward_list<int>{1,2,3}; std::cout << l.size(); // error, no size() member auto cit0 = begin(l); auto cit1 = next(cit0); auto cit2 = prev(cit1); // error, not bidirectional
  • 309.
    204 array auto a1 =std::array<int, 3>{}; const auto cit = a1.cbegin(); const auto& x = a1[2]; // use like a C-array const auto& y = a1.at(10); // std::out_of_range auto a2 = std::array<int, 3>{}; if (a1 == a2) { // ... } auto a3 = a1; // etc. tableaux C pour le C++ strictement les mêmes performances! à privilégier dans tout nouveau code
  • 310.
    205 array const auto a0= std::array<int, 3>{1,2,3}; const auto a1 = a0; toutes les facilités du C++ (1) const int t0[3] = {1,2,3}; int t1[3]; std::copy(t0, t0 + 3, t1); vs copie
  • 311.
    205 array const auto a0= std::array<int, 3>{1,2,3}; const auto a1 = a0; toutes les facilités du C++ (1) dangereux const int t0[3] = {1,2,3}; int t1[3]; std::copy(t0, t0 + 3, t1); vs copie
  • 312.
    206 array const auto a0= std::array<int, 3>{1,2,3}; const auto res = std::find(begin(a0), end(a0), 3); if (res != end(a0)) { std::cout << "foundn"; } toutes les facilités du C++ (2) utilisation des algorithmes STL
  • 313.
    207 array auto a0 =std::array<int, 3>{1, 2, 3}; auto a1 = std::array<int, 3>{1, 99, 3}; const auto res = memcmp(a0.data(), a1.data(), 3 * sizeof(int)); std::cout << res << 'n'; memcpy(a0.data(), a1.data(), 3 * sizeof(int)); for (auto x : a0) { std::cout << x << 'n'; } utilisation avec les fonctions C .data() pour accéder au tableau C
  • 314.
    208 tuple conteneur de typeshétérogène auto t1 = std::tuple<int, char, foo>{2, 'a', foo{}}; auto t2 = std::make_tuple(2, 'a', foo{}); const auto& f = std::get<2>(t2);
  • 315.
    209 tuple permet de retournerplusieurs valeurs sans avoir à créer un nouveau type (1) struct foo {}; std::tuple<int, foo> bar() { return std::make_tuple(2, foo{}); } const auto t = bar();
  • 316.
    210 tuple permet de retournerplusieurs valeurs sans avoir à créer un nouveau type (2) struct foo {}; std::tuple<int, foo> bar() { return std::make_tuple(2, foo{}); } auto i = 0; auto f = foo{}; std::tie(i, f) = bar(); std::cout << i; // 2
  • 317.
    211 tuple concaténer des tuples autot0 = std::make_tuple(1, foo{}, 'a'); std::cout << std::tuple_size<decltype(t0)>::value; // 3 auto t1 = std::make_tuple(3.14, std::string{"abc"}); std::cout << std::tuple_size<decltype(t1)>::value; // 2 auto t2 = std::tuple_cat(t0, t1); std::cout << std::tuple_size<decltype(t2)>::value; // 5
  • 318.
    212 unordered_map table de hachage automap = std::unordered_map<int, std::string>{}; map.insert({42, "abc"}); map.emplace(33, "def"); for (const auto& key_value : map) { std::cout << key_value.first << " -> " << key_value.second; } fonctionne comme std::map
  • 319.
    213 unordered_map fonctions spécifiques auxtables de hachage auto map = std::unordered_map<int, std::string>{1'000'000}; peut-être initialisée avec un nombre de buckets map.rehash(2'000'000); peut-être redimensionnée map.load_factor() afficher le facteur de charge
  • 320.
    214 unordered_map utiliser un typepersonnalisé en tant que clé namespace std { template <> struct hash<foo> { std::size_t operator()(const foo& f) const { // hash function } }; } bool operator==(const foo&, const foo&); struct foo { // some data }; clé égalité hachage
  • 321.
    214 unordered_map utiliser un typepersonnalisé en tant que clé namespace std { template <> struct hash<foo> { std::size_t operator()(const foo& f) const { // hash function } }; } bool operator==(const foo&, const foo&); struct foo { // some data }; clé égalité hachage spécialisation
  • 322.
    215 unordered_map bonus: combiner desvaleurs de hachage (1) /// @brief Combine the hash value of x with seed. /// /// Taken from <boost/functional/hash.hpp>. /// Sligthy modified to use std::hash<T> instead of /// boost::hash_value(). template <typename T> inline void hash_combine(std::size_t& seed, const T& x) noexcept(noexcept(std::hash<T>()(x))) { seed ^= std::hash<T>()(x) + 0x9e3779b9 + (seed<<6) + (seed>>2); }
  • 323.
    216 unordered_map bonus: combiner desvaleurs de hachage (2) namespace std { template <> struct hash<foo> { std::size_t operator()(const foo& f) const { auto seed = 0ul; hash_combine(seed, f.x); hash_combine(seed, f.y); return seed; } }; }
  • 324.
    217 Gestion de lamémoire • introduction de unique_ptr ‣ auto_ptr deprecated ‣ utilise les portées pour désallouer • introduction de shared_ptr ‣ déjà présent dans Boost et POCCO ‣ comptage de référence • désallocation automatique dans tous les cas
  • 325.
    218 auto_ptr struct foo { ~foo(){std::cout <<"~foo()n";} }; int main() { { auto ptr1 = std::auto_ptr<foo>{new foo}; std::cout << ptr1.get() << 'n'; } std::cout << "after scopen"; } désallocation automatique à la sortie la portée 0x7fc1e1c00080 ~foo() after scope
  • 326.
    218 auto_ptr struct foo { ~foo(){std::cout <<"~foo()n";} }; int main() { { auto ptr1 = std::auto_ptr<foo>{new foo}; std::cout << ptr1.get() << 'n'; } std::cout << "after scopen"; } désallocation automatique à la sortie la portée 0x7fc1e1c00080 ~foo() after scope désallocation ici
  • 327.
    219 auto_ptr auto ptr1 =std::auto_ptr<int>{new int{42}}; std::cout << ptr1.get(); // 0x7ff01a400080 auto ptr2 = ptr1; std::cout << ptr1.get(); // 0x0 std::cout << ptr2.get(); // 0x7ff01a400080 le problème (1) ptr2 est une copie de ptr1 et pourtant ce dernier perd la propriété du pointeur sémantique peu claire!
  • 328.
    220 auto_ptr auto vec =std::vector<std::auto_ptr<foo>>{}; auto ptr = std::auto_ptr<foo>{new foo}; vec.push_back(ptr); auto x = vec.front(); // vec.front() is now nullptr. le problème (2) avec un conteneur STL
  • 329.
    221 unique_ptr désallocation automatique àla sortie la portée struct foo { ~foo(){std::cout << "~foo()n";} }; int main() { { auto ptr1 = std::unique_ptr<foo>{new foo}; std::cout << ptr1.get() << 'n'; } std::cout << "after scopen"; } 0x7fc1e1c00080 ~foo() after scope
  • 330.
    221 unique_ptr désallocation automatique àla sortie la portée struct foo { ~foo(){std::cout << "~foo()n";} }; int main() { { auto ptr1 = std::unique_ptr<foo>{new foo}; std::cout << ptr1.get() << 'n'; } std::cout << "after scopen"; } 0x7fc1e1c00080 ~foo() after scope désallocation ici
  • 331.
    222 unique_ptr résoudre le problèmede auto_ptr auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = ptr1; error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int, std::__1::default_delete<int> >'   auto ptr2 = ptr1;        ^      ~~~~ interdire la copie transfert de propriété par déplacement auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = std::move(ptr1); // transfert ownership
  • 332.
    222 unique_ptr résoudre le problèmede auto_ptr auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = ptr1; error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int, std::__1::default_delete<int> >'   auto ptr2 = ptr1;        ^      ~~~~ ! interdire la copie transfert de propriété par déplacement auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = std::move(ptr1); // transfert ownership
  • 333.
    222 unique_ptr résoudre le problèmede auto_ptr auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = ptr1; error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int, std::__1::default_delete<int> >'   auto ptr2 = ptr1;        ^      ~~~~ ! interdire la copie transfert de propriété par déplacement auto ptr1 = std::unique_ptr<int>{new int{42}}; auto ptr2 = std::move(ptr1); // transfert ownership un unique propriétaire du pointeur : ptr1, puis ptr2
  • 334.
    223 unique_ptr exemple de fabrique structfoo {}; std::unique_ptr<foo> factory() { return std::unique_ptr<foo>{new foo{}}; } pas de problème de copie de foo ou de responsabilité de foo*
  • 335.
    224 unique_ptr utilisation comme unpointeur normal struct foo { int x; }; auto ptr = std::unique_ptr<foo>(new foo{42}); *ptr = foo{33}; ptr->x = 2;
  • 336.
    225 unique_ptr C++14 auto ptr =std::unique_ptr<int>(new int{42}); Création d’un unique_ptr C++11 auto ptr = std::make_unique<int>(42);
  • 337.
    226 unique_ptr auto a =std::make_unique<int[]>(3); Pour un tableau de taille inconnue à la compilation auto ptr = std::unique_ptr<int[]>{new int[3]}; ptr[0] = 1; ptr[1] = 0; ptr[2] = 3; std::cout << ptr[1]; Utilisation
  • 338.
    227 shared_ptr ressource partagée parplusieurs propriétaires struct foo { ~foo(){std::cout << "~foo()n";} }; int main() { auto ptr = std::shared_ptr<foo>(new foo{}); { auto ptr2 = ptr; } std::cout << "scope exitn"; }
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
    229 shared_ptr auto f1 =std::shared_ptr<foo>(new foo{}); auto f2 = std::make_shared<foo>(); objectif : plus un seul new dans le code facilité de création : make_shared
  • 347.
    230 shared_ptr const auto ptr= std::make_shared<foo>(); const auto ptr = std::shared_ptr<foo>(new foo{}); performances préférez à
  • 348.
    230 shared_ptr const auto ptr= std::make_shared<foo>(); const auto ptr = std::shared_ptr<foo>(new foo{}); performances préférez à 1 allocation
  • 349.
    230 shared_ptr const auto ptr= std::make_shared<foo>(); const auto ptr = std::shared_ptr<foo>(new foo{}); performances préférez à 1 allocation 2 allocations
  • 350.
    231 shared_ptr utilisation comme unpointeur normal struct foo { int x; }; auto ptr = std::make_shared<foo>(42); *ptr = foo{33}; ptr->x = 2;
  • 351.
    232 shared_ptr cycles struct foo { std::shared_ptr<foo> link; ~foo(){std::cout<< "~foon";} }; auto f1 = std::make_shared<foo>(); auto f2 = std::make_shared<foo>(); f1->link = f2; f2->link = f1;
  • 352.
    233 shared_ptr casser les cyclesavec weak_ptr struct foo { std::weak_ptr<foo> link; ~foo(){std::cout << "~foon";} }; auto f1 = std::make_shared<foo>(); auto f2 = std::make_shared<foo>(); f1->link = f2; f2->link = f1;
  • 353.
    234 shared_ptr utilisation de weak_ptr structfoo { std::weak_ptr<foo> link; void operator()() const { if (const auto shared = link.lock()) { // ok, do something with shared } else { // link is no longer valid } } };
  • 354.
    234 shared_ptr utilisation de weak_ptr structfoo { std::weak_ptr<foo> link; void operator()() const { if (const auto shared = link.lock()) { // ok, do something with shared } else { // link is no longer valid } } }; renvoie un shared_ptr
  • 355.
    235 shared_ptr accès au nombrede référencement const auto ptr1 = std::shared_ptr<foo>(new foo{}); std::cout << ptr1.use_count(); // “1” const auto ptr2 = ptr1; std::cout << ptr1.use_count(); // “2” std::cout << ptr2.use_count(); // “2”
  • 356.
    236 shared_ptr auto ptr =std::make_shared<int[3]>{new int[3]}; ne fonctionne pas avec les tableaux ! auto ptr = std::make_shared<std::array<int, 3>>(); shared_ptr<array> alternatives std::vector auto ptr = std::make_shared<std::vector<int>>();
  • 357.
    237 shared_ptr factory avec cache structfoo { ~foo(){std::cout << “~foo()";} }; std::shared_ptr<foo> mk_foo(int x) { // ??? } int main() { const auto foo_ptr1 = mk_foo(42); const auto foo_ptr2 = mk_foo(42); std::cout << "main exitn"; }
  • 358.
    237 shared_ptr factory avec cache structfoo { ~foo(){std::cout << “~foo()";} }; std::shared_ptr<foo> mk_foo(int x) { static auto cache = std::unordered_map<int, std::shared_ptr<foo>>{}; const auto search = cache.find(x); if (search == end(cache)) { const auto tmp = std::make_shared<foo>(); return cache.emplace(x, tmp).first->second; } else { return search->second; } } int main() { const auto foo_ptr1 = mk_foo(42); const auto foo_ptr2 = mk_foo(42); std::cout << "main exitn"; }
  • 359.
    238 shared_ptr/unique_ptr Propriétaires 1 n? unique_ptr ✔ shared_ptr ✔ ✔ ✔ en résumé
  • 360.
    239 shared_ptr/unique_ptr exception-safe code try { const autoptr = new int{42}; foo(ptr); delete ptr; } catch (...) {} fuite mémoire
  • 361.
    240 shared_ptr/unique_ptr exception-safe code try { const autoptr = std::make_shared<int>(42); foo(ptr); } catch (...) {} fuite mémoire évitée
  • 362.
    241 shared_ptr/unique_ptr struct bar {bar(){throw std::runtime_error("");}}; struct foo { int* i_; bar br_; foo() : i_{new int{42}} , br_{} {} ~foo() {delete i_;} }; try { const auto f = foo{}; } catch (...) {} exception-safe code, cas des constructeurs (1)
  • 363.
    241 shared_ptr/unique_ptr struct bar {bar(){throw std::runtime_error("");}}; struct foo { int* i_; bar br_; foo() : i_{new int{42}} , br_{} {} ~foo() {delete i_;} }; try { const auto f = foo{}; } catch (...) {} jamais appelé exception-safe code, cas des constructeurs (1)
  • 364.
    242 shared_ptr/unique_ptr exception-safe code, casdes constructeurs (2) struct bar {bar() {throw std::runtime_error("");}}; struct foo { std::shared_ptr<int> i_; bar br_; foo() : i_{std::make_shared<int>(42)} , br_{} {} ~foo() {} // never called }; try { const auto f = foo{}; } catch (...) {}
  • 365.
    243 shared_ptr/unique_ptr exception-safe code, casdes constructeurs (3) struct foo { int* i_; bar br_; foo() try : i_{new int{42}} , br_{} {} catch (...) { delete i_; } ~foo() {delete i_;} // never called }; autre solution…
  • 366.
    244 function pour remplacer lespointeurs de fonction int bar(int x) {return x * 3;} struct foo { int operator()(int x) const {return x + 1;} }; using function_type = std::function<int (int)>; // free function auto fun1 = function_type{bar}; // function object auto fun2 = function_type{foo{}}; // lambda auto fun3 = function_type{[](int x){return x - 1;}}; std::cout << fun1(3) << ‘n'; // 9 std::cout << fun2(3) << ‘n'; // 4 std::cout << fun3(3) << ‘n'; // 2
  • 367.
    244 function pour remplacer lespointeurs de fonction int bar(int x) {return x * 3;} struct foo { int operator()(int x) const {return x + 1;} }; using function_type = std::function<int (int)>; // free function auto fun1 = function_type{bar}; // function object auto fun2 = function_type{foo{}}; // lambda auto fun3 = function_type{[](int x){return x - 1;}}; std::cout << fun1(3) << ‘n'; // 9 std::cout << fun2(3) << ‘n'; // 4 std::cout << fun3(3) << ‘n'; // 2 type de retour
  • 368.
    244 function pour remplacer lespointeurs de fonction int bar(int x) {return x * 3;} struct foo { int operator()(int x) const {return x + 1;} }; using function_type = std::function<int (int)>; // free function auto fun1 = function_type{bar}; // function object auto fun2 = function_type{foo{}}; // lambda auto fun3 = function_type{[](int x){return x - 1;}}; std::cout << fun1(3) << ‘n'; // 9 std::cout << fun2(3) << ‘n'; // 4 std::cout << fun3(3) << ‘n'; // 2 type de retour paramètres
  • 369.
    245 function les fonctions deviennentdes valeurs comme les autres enum class op_type{add, diff}; // A function factory std::function<int (int)> mk_fun(op_type op, int value) { switch (op) { case op_type::add : return [=](int x){return value + x;}; case op_type::diff: return [=](int x){return value - x;}; } } const auto fun0 = mk_fun(op_type::add, 1); std::cout << fun0(1) << ‘n'; // 2 const auto fun1 = mk_fun(op_type::diff, 1); std::cout << fun1(1) << ‘n'; // 0 “programmation fonctionnelle”
  • 370.
    245 function les fonctions deviennentdes valeurs comme les autres enum class op_type{add, diff}; // A function factory std::function<int (int)> mk_fun(op_type op, int value) { switch (op) { case op_type::add : return [=](int x){return value + x;}; case op_type::diff: return [=](int x){return value - x;}; } } const auto fun0 = mk_fun(op_type::add, 1); std::cout << fun0(1) << ‘n'; // 2 const auto fun1 = mk_fun(op_type::diff, 1); std::cout << fun1(1) << ‘n'; // 0 “programmation fonctionnelle” capture par copie
  • 371.
    246 function fonctions membres struct baz { intdo_it(int x, int y) const {return x + y;} }; auto fun4 = std::function<int (const baz&, int, int)>{&baz::do_it}; const auto b = baz{}; // same as b.do_it(3, 4) std::cout << fun4(b, 3, 4); // 7
  • 372.
    246 function fonctions membres struct baz { intdo_it(int x, int y) const {return x + y;} }; auto fun4 = std::function<int (const baz&, int, int)>{&baz::do_it}; const auto b = baz{}; // same as b.do_it(3, 4) std::cout << fun4(b, 3, 4); // 7 this
  • 373.
    247 bind int add(int x,int y) { return x + y; } using namespace std::placeholders; // _1, _2, etc. const auto fun = std::bind(add, 1, _1); std::cout << fun(2); // 3 pour “lier” (fixer) des paramètres
  • 374.
    247 bind int add(int x,int y) { return x + y; } using namespace std::placeholders; // _1, _2, etc. const auto fun = std::bind(add, 1, _1); std::cout << fun(2); // 3 pour “lier” (fixer) des paramètres x est fixé à 1
  • 375.
    247 bind int add(int x,int y) { return x + y; } using namespace std::placeholders; // _1, _2, etc. const auto fun = std::bind(add, 1, _1); std::cout << fun(2); // 3 pour “lier” (fixer) des paramètres x est fixé à 1 y reste libre
  • 376.
    247 bind int add(int x,int y) { return x + y; } using namespace std::placeholders; // _1, _2, etc. const auto fun = std::bind(add, 1, _1); std::cout << fun(2); // 3 pour “lier” (fixer) des paramètres x est fixé à 1 y reste libre 2 est passé à y
  • 377.
    248 bind int add(int x,int y) { return x + y; } const auto fun = [](int z){return add(1, z);}; std::cout << fun(2); // 3 lambda équivalent
  • 378.
    248 bind int add(int x,int y) { return x + y; } const auto fun = [](int z){return add(1, z);}; std::cout << fun(2); // 3 lambda équivalent x est fixé à 1
  • 379.
    249 bind struct foo { int operator()(intx, int y) const {return x + y;} }; using namespace std::placeholders; const auto f = foo{}; const auto fun = std::bind(&foo::operator(), f, 3, _1); std::cout << fun(2); pour lier une méthode à un objet
  • 380.
    250 bind les paramètres sontpassés par copie void baz(int& x) { ++x; } auto i = 0; auto fun = std::bind(baz, i); std::cout << i; // 0 fun(); // call baz() std::cout << i; // 0
  • 381.
    250 bind les paramètres sontpassés par copie void baz(int& x) { ++x; } auto i = 0; auto fun = std::bind(baz, i); std::cout << i; // 0 fun(); // call baz() std::cout << i; // 0 par valeur
  • 382.
    251 bind void baz(int& x) { ++x; } autoi = 0; auto fun = std::bind(baz, std::ref(i)); std::cout << i; // 0 fun(); // call baz() std::cout << i; // 1 modifier les paramètres fixés
  • 383.
    251 bind void baz(int& x) { ++x; } autoi = 0; auto fun = std::bind(baz, std::ref(i)); std::cout << i; // 0 fun(); // call baz() std::cout << i; // 1 modifier les paramètres fixés par référence
  • 384.
    252 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<foo&>{f0, f1}; comment créer un conteneur de références? (1)
  • 385.
    252 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<foo&>{f0, f1}; ne compile pas comment créer un conteneur de références? (1)
  • 386.
    253 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<foo*>{&f0, &f1}; comment créer un conteneur de références? (2)
  • 387.
    253 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<foo*>{&f0, &f1}; comment créer un conteneur de références? (2) il faut utiliser un pointeur
  • 388.
    254 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1}; for (auto& f : vec) { f.get().data = 5; } std::cout << f0.data << " " << f1.data << ‘n'; // 5 5 comment créer un conteneur de références? (3)
  • 389.
    255 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1}; for (foo& f : vec) { f.data = 5; } std::cout << f0.data << " " << f1.data << ‘n'; // 5 5 comment créer un conteneur de références? (4)
  • 390.
    255 reference_wrapper struct foo { int data; }; autof0 = foo{0}; auto f1 = foo{1}; auto vec = std::vector<std::reference_wrapper<foo>>{f0, f1}; for (foo& f : vec) { f.data = 5; } std::cout << f0.data << " " << f1.data << ‘n'; // 5 5 comment créer un conteneur de références? (4) conversion vers foo
  • 391.
    256 enable_if sélectionner statiquement unefonction template <bool Cond> typename std::enable_if<Cond, void>::type foo() { std::cout << "Condn"; } template <bool Cond> typename std::enable_if<not Cond, void>::type foo() { std::cout << "not Condn"; } foo<true>(); // Cond foo<false>(); // not Cond
  • 392.
    256 enable_if sélectionner statiquement unefonction template <bool Cond> typename std::enable_if<Cond, void>::type foo() { std::cout << "Condn"; } template <bool Cond> typename std::enable_if<not Cond, void>::type foo() { std::cout << "not Condn"; } foo<true>(); // Cond foo<false>(); // not Cond type de retour type de retour
  • 393.
    257 enable_if écriture simplifiée template <boolCond> enable_if_t<Cond, void> foo() { std::cout << "Condn"; } template <bool Cond> enable_if_t<not Cond, void> foo() { std::cout << "not Condn"; } foo<true>(); // Cond foo<false>(); // not Cond template <bool Cond, typename Res> using enable_if_t = typename std::enable_if<Cond, Res>::type;
  • 394.
    257 enable_if écriture simplifiée template <boolCond> enable_if_t<Cond, void> foo() { std::cout << "Condn"; } template <bool Cond> enable_if_t<not Cond, void> foo() { std::cout << "not Condn"; } foo<true>(); // Cond foo<false>(); // not Cond template <bool Cond, typename Res> using enable_if_t = typename std::enable_if<Cond, Res>::type; défini en C++14
  • 395.
    258 type_traits • trait :un petit objet dont le but est de contenir des informations à propos d’autres types ‣ pour connaître des détails d’implantation ‣ pour déterminer des politiques d’implantation
  • 396.
    259 type_traits std::cout << std::boolalpha; std::cout<< std::is_pointer<int*>::value << ‘n'; // true std::cout << std::is_pointer<int>::value << ‘n'; // false std::cout << std::is_class<foo>::value << ‘n'; // true std::cout << std::is_class<int>::value << ‘n'; // false std::cout << std::is_signed<int>::value << ‘n'; // true std::cout << std::is_signed<unsigned int>::value << ‘n'; // false
  • 397.
    259 type_traits std::cout << std::boolalpha; std::cout<< std::is_pointer<int*>::value << ‘n'; // true std::cout << std::is_pointer<int>::value << ‘n'; // false std::cout << std::is_class<foo>::value << ‘n'; // true std::cout << std::is_class<int>::value << ‘n'; // false std::cout << std::is_signed<int>::value << ‘n'; // true std::cout << std::is_signed<unsigned int>::value << ‘n'; // false etc.
  • 398.
    260 type_traits template <typename T> enable_if_t<std::is_integral<T>::value,void> json(const T& x) { std::cout << x; } template <typename T> enable_if_t<not std::is_integral<T>::value, void> json(const T& x) { std::cout << '"' << x << '"'; } json(42); // 42 json(“42"); // “42” json(“abc”); // “abc”
  • 399.
    260 type_traits template <typename T> enable_if_t<std::is_integral<T>::value,void> json(const T& x) { std::cout << x; } template <typename T> enable_if_t<not std::is_integral<T>::value, void> json(const T& x) { std::cout << '"' << x << '"'; } json(42); // 42 json(“42"); // “42” json(“abc”); // “abc” exemple : export JSON
  • 400.
    260 type_traits template <typename T> enable_if_t<std::is_integral<T>::value,void> json(const T& x) { std::cout << x; } template <typename T> enable_if_t<not std::is_integral<T>::value, void> json(const T& x) { std::cout << '"' << x << '"'; } json(42); // 42 json(“42"); // “42” json(“abc”); // “abc” exemple : export JSON si T n’est pas un type numérique
  • 401.
    260 type_traits template <typename T> enable_if_t<std::is_integral<T>::value,void> json(const T& x) { std::cout << x; } template <typename T> enable_if_t<not std::is_integral<T>::value, void> json(const T& x) { std::cout << '"' << x << '"'; } json(42); // 42 json(“42"); // “42” json(“abc”); // “abc” exemple : export JSON si T n’est pas un type numérique si T est un type numérique
  • 402.
    261 random • Deux typesde composants ‣ Générateurs de nombres aléatoires - matériel - pseudo-aléatoire • en général, le matériel est utilisé en tant que seed pour les générateurs pseudo-aléatoires ‣ Distributions statistiques - uniforme, normal, Poisson, etc.
  • 403.
    262 random std::random_device rd; std::cout <<rd() << 'n'; std::cout << rd() << 'n'; std::uniform_int_distribution<int> uniform_dist{1, 5}; std::cout << uniform_dist(rd) << 'n'; std::cout << uniform_dist(rd) << 'n'; std::cout << uniform_dist(rd) << 'n'; exemple (1)
  • 404.
    263 random std::random_device rd; // Seedengine with hardware std::default_random_engine gen{rd()}; std::cout << gen() << 'n'; std::cout << gen() << 'n'; std::normal_distribution<> gaussian; std::cout << gaussian(gen) << 'n'; std::cout << gaussian(gen) << 'n'; std::cout << gaussian(gen) << 'n'; exemple (2)
  • 405.
    264 chrono • Trois composants ‣durées ‣ horloges - système - stable - haute résolution ‣ points dans le temps
  • 406.
    265 chrono auto start =std::chrono::time_point<std::chrono::steady_clock>{}; auto end = std::chrono::time_point<std::chrono::steady_clock>{}; start = std::chrono::steady_clock::now(); // do something here end = std::chrono::steady_clock::now(); const auto elapsed = end - start; const auto s = std::chrono::duration<double>{elapsed}; const auto ms = std::chrono::duration<double, std::milli>{elapsed}; std::cout << "elapsed time: " << ms.count() << "msn"; std::cout << "elapsed time: " << s.count() << "sn"; exemple
  • 407.
    265 chrono auto start =std::chrono::time_point<std::chrono::steady_clock>{}; auto end = std::chrono::time_point<std::chrono::steady_clock>{}; start = std::chrono::steady_clock::now(); // do something here end = std::chrono::steady_clock::now(); const auto elapsed = end - start; const auto s = std::chrono::duration<double>{elapsed}; const auto ms = std::chrono::duration<double, std::milli>{elapsed}; std::cout << "elapsed time: " << ms.count() << "msn"; std::cout << "elapsed time: " << s.count() << "sn"; exemple à favoriser pour mesurer des écoulements de temps
  • 408.
    266 regex const auto str= std::string{"abc ABC 123"}; const auto number = std::regex{"d+"}; if (std::regex_search(str, number)) { std::cout << "Number foundn"; } déterminer la présence d’une séquence
  • 409.
    267 regex const auto str= std::string{"abc ABC 123"}; const auto words = std::regex{"([a-zA-Z]+)"}; auto words_it = std::sregex_iterator{begin(str), end(str), words}; const auto words_end = std::sregex_iterator{}; for (; words_it != words_end; ++words_it) { std::cout << words_it->str() << 'n'; } afficher les séquences trouvées
  • 410.
    268 regex const auto str= std::string{"abc ABC 123"}; const auto uppercase = std::regex{"[A-Z]"}; std::regex_replace( std::ostreambuf_iterator<char>(std::cout) , begin(str), end(str), uppercase, "-"); std::cout << std::regex_replace(str, uppercase, "|$&|") << 'n'; remplacer du texte
  • 411.
    269 regex Some people, whenconfronted with a problem, think “I know, I'll use regular expressions.” Now they have two problems.
  • 412.
  • 413.
    Optimiser 271 • Que faut-iloptimiser? ‣ algorithmes ‣ mémoire • À quel prix? ‣ portabilité ‣ maintenance ‣ possible complication du code • Systèmes embarqué ≠ serveur ≠ PC • Compilateurs • Profiler avant d’optimiser
  • 414.
    Copy-elision 272 • Des copiespeuvent-être évitées par le compilateur ‣ retour par valeur ‣ temporaire passé par valeur ‣ exception capturée par valeur • A le droit d’affecter les potentiels effets de bords
  • 415.
    Copy-elision 273 struct foo { int data; foo(intd) : data{d} {std::cout << "foo(int)n";} foo(const foo&) {std::cout << "foo(const foo&)n";} foo& operator=(const foo&) {std::cout << "operator=(const foo&)n"; return *this;} foo(foo&&) {std::cout << "foo(foo&&)n";} foo& operator=(foo&&) {std::cout << "operator=(foo&&)n"; return *this;} }; structure témoin
  • 416.
    foo named_return_by_value() { auto f =foo{3}; f.data = 42; return f; } foo named_return_by_value2(int x) { auto f = foo{0}; f.data = x > 10 ? 0 : 1; return f; } const auto f0 = named_return_by_value(); std::cout << f0.data << ‘n'; const auto f1 = named_return_by_value2(); std::cout << f1.data << ‘n'; Copy-elision 274 retour d’un objet nommé par valeur
  • 417.
    foo named_return_by_value() { auto f =foo{3}; f.data = 42; return f; } foo named_return_by_value2(int x) { auto f = foo{0}; f.data = x > 10 ? 0 : 1; return f; } const auto f0 = named_return_by_value(); std::cout << f0.data << ‘n'; const auto f1 = named_return_by_value2(); std::cout << f1.data << ‘n'; Copy-elision 274 retour d’un objet nommé par valeur pas de copie pas de copie
  • 418.
  • 419.
  • 420.
    Copy-elision 276 foo different_return_values(int x) { const autof0 = foo{0}; const auto f1 = foo{1}; return (x < 10) ? f0 : f1; } const auto f = different_return_values(10); il faut renvoyer le même objet nommé
  • 421.
    Copy-elision 276 foo different_return_values(int x) { const autof0 = foo{0}; const auto f1 = foo{1}; return (x < 10) ? f0 : f1; } const auto f = different_return_values(10); il faut renvoyer le même objet nommé copie deux objets différents
  • 422.
    C++11 277 • Optimisations “gratuites”en passant au C++11 • Rvalues references • Utilisation de std::move ‣ si la copy-elision échoue ‣ réallocation sur un push_back() ‣ …
  • 423.
    C++11 278 struct foo { foo(){std::cout << "foo()n";} foo(const foo&) {std::cout << "foo(const foo&)n";} foo& operator=(const foo&) {std::cout << "operator=(const foo&)n"; return *this;} #if __cplusplus >= 201103L foo(foo&&) {std::cout << "foo(foo&&)n";} foo& operator=(foo&&) {std::cout << "operator=(foo&&)n"; return *this;} #endif }; std::vector<foo> fun() { std::vector<foo> vec; vec.push_back(foo()); return vec; } std::vector<foo> vec; vec = fun(); C++03 C++11 foo() foo(foo&&) foo() foo(const foo&) foo(const foo&) foo() foo(const foo&) gcc/clang Visual Studio exemple
  • 424.
    Performances CPU/RAM 279 Slide 17 CPU/Memoryperformance Computer architecture: a quantitative approach By John L. Hennessy, David A. Patterson, Andrea C. Arpaci-Dusseau ©Tony Albrecht - Pitfalls of Object Oriented Programming
  • 425.
    Mémoire contigüe 280 int tab[2][3] i+1i+2 j+1 j+2 j+3 j+1 j+2 j+3 i j
  • 426.
    Allocations 281 • Les allocationsmémoires dans le tas sont coûteuses ‣ l’allocateur doit trouver un emplacement libre et de taille suffisante en mémoire ‣ ressource abondante • Privilégier les allocations sur la pile ‣ coûte seulement un déplacement du pointeur de pile ‣ mais c’est une ressource limitée • Systèmes embarqués
  • 427.
    282 .reserve() petit rappel… auto vec= std::vector<int>{}; for (auto i = 0ul; i < 8192; ++i) { vec.push_back(i); } auto vec = std::vector<int>{}; vec.reserve(8192); for (auto i = 0ul; i < 8192; ++i) { vec.push_back(i); } 0.044 ms 0.014 ms
  • 428.
    283 .reserve() si la taillen’est vraiment pas estimable à l’avance : std::deque auto vec = std::vector<int>{}; for (auto i = 0ul; i < 8192; ++i) { vec.push_back(i); } auto vec = std::vector<int>{}; vec.reserve(8192); for (auto i = 0ul; i < 8192; ++i) { vec.push_back(i); } 0.044 ms 0.014 ms auto deq = std::deque<int>{}; for (auto i = 0ul; i < 8192; ++i) { deq(i); } 0.022 ms(gcc, clang)
  • 429.
    Réutilisation d’objets alloués 284 for(auto i = 0ul; i < 1000; ++i) { auto ptr = std::make_shared<int>(); *ptr = i; } auto ptr = std::make_shared<int>(); for (auto i = 0ul; i < 1000; ++i) { *ptr = i; } autre petit rappel… 0.002 ms 0.059 ms
  • 430.
    Éviter les allocationsrépétées d’un même type 285 auto mem = std::vector<foo>(1000); auto foos = std::vector<foo*>{}; foos.reserve(1000); for (auto i = 0ul; i < 1000; ++i) { foos.push_back(new foo); } 0.001 ms 0.044 ms
  • 431.
    Empty base classoptimisation 286 struct func { void operator()() const noexcept {} }; struct a { func f; int x; }; struct b : public func { int x; }; std::cout << sizeof(func) << ‘n'; // 1 std::cout << sizeof(a) << ‘n'; // 8 std::cout << sizeof(b) << ‘n'; // 4 ne pas gaspiller de l’espace mémoire
  • 432.
    Empty base classoptimisation 286 struct func { void operator()() const noexcept {} }; struct a { func f; int x; }; struct b : public func { int x; }; std::cout << sizeof(func) << ‘n'; // 1 std::cout << sizeof(a) << ‘n'; // 8 std::cout << sizeof(b) << ‘n'; // 4 classe vide (pas de données) ne pas gaspiller de l’espace mémoire
  • 433.
    Empty base classoptimisation 286 struct func { void operator()() const noexcept {} }; struct a { func f; int x; }; struct b : public func { int x; }; std::cout << sizeof(func) << ‘n'; // 1 std::cout << sizeof(a) << ‘n'; // 8 std::cout << sizeof(b) << ‘n'; // 4 classe vide (pas de données) EBO ne pas gaspiller de l’espace mémoire
  • 434.
    Optimisez pour lamémoire 287
  • 435.
  • 436.
  • 437.
    290 Bibliothèque portable dethreads • Thread • Mutex • Async • Futures/Promises • Atomic • Lock-free ‣ Modèle mémoire standardisé
  • 438.
    291 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; std::cout << "waiting for t " << t.get_id() << std::endl; t.join(); std::cout << "t has ended" << std::endl; return 0; } création
  • 439.
    291 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; std::cout << "waiting for t " << t.get_id() << std::endl; t.join(); std::cout << "t has ended" << std::endl; return 0; } fonction à appeler création
  • 440.
    291 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; std::cout << "waiting for t " << t.get_id() << std::endl; t.join(); std::cout << "t has ended" << std::endl; return 0; } fonction à appeler paramètres de la fonction création
  • 441.
    292 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; // t.join(); return 0; } libc++abi.dylib: terminating attention à la terminaison à la destruction d’un thread, celui-ci doit-être détaché ou terminé
  • 442.
    292 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; // t.join(); return 0; } libc++abi.dylib: terminating ~thread() attention à la terminaison à la destruction d’un thread, celui-ci doit-être détaché ou terminé
  • 443.
    293 thread #include <chrono> #include <iostream> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { auto t = std::thread{sleep, 1000}; t.detach(); return 0; } détacher un thread
  • 444.
    294 mutex #include <chrono> #include <functional> #include<iostream> #include <thread> #include <mutex> void fun(std::mutex& mutex) { mutex.lock(); std::cout << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds{1000}); mutex.unlock(); } int main () { std::mutex m; std::thread t0{fun, std::ref(m)}; std::thread t1{std::bind(fun, std::ref(m))}; t0.join(); t1.join(); return 0; }
  • 445.
    294 mutex #include <chrono> #include <functional> #include<iostream> #include <thread> #include <mutex> void fun(std::mutex& mutex) { mutex.lock(); std::cout << std::this_thread::get_id() << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds{1000}); mutex.unlock(); } int main () { std::mutex m; std::thread t0{fun, std::ref(m)}; std::thread t1{std::bind(fun, std::ref(m))}; t0.join(); t1.join(); return 0; } un mutex ne peut pas être copié
  • 446.
    295 mutex void fun(std::mutex& mutex,int depth) { if (depth == 0) return; mutex.lock(); fun(mutex, depth - 1); mutex.unlock(); } std::mutex m; std::thread t0{fun, std::ref(m), 3}; quel est le problème?
  • 447.
    296 recursive_mutex void fun(std::recursive_mutex& mutex,int depth) { if (depth == 0) return; mutex.lock(); fun(mutex, depth - 1); mutex.unlock(); } std::recursive_mutex m; std::thread t0{fun, std::ref(m), 3}; verrouillage multiple par le même thread autorisé
  • 448.
    297 timed_mutex void fun(std::timed_mutex& mutex) { if(mutex.try_lock_for(std::chrono::milliseconds{500})) { std::cout << ":-)" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds{1500}); mutex.unlock(); } else { std::cout << ":-(" << std::endl; } } mutex avec attente bornée
  • 449.
    298 mutex void fun(std::mutex& mutex) { try { mutex.lock(); sub_fun();// throw std::runtime_error{""}; mutex.unlock(); } catch (...) { mutex.unlock(); } } en cas d’exception…
  • 450.
    298 mutex void fun(std::mutex& mutex) { try { mutex.lock(); sub_fun();// throw std::runtime_error{""}; mutex.unlock(); } catch (...) { mutex.unlock(); } } et si on l’oublie? interblocage… en cas d’exception…
  • 451.
    299 lock_guard try { std::lock_guard<std::mutex> lock{mutex}; sub_fun(); //throw std::runtime_error{""}; } catch (...) {} déverrouillage automatique à la sortie de la portée à privilégier
  • 452.
    299 lock_guard try { std::lock_guard<std::mutex> lock{mutex}; sub_fun(); //throw std::runtime_error{""}; } catch (...) {} déverrouillage automatique à la sortie de la portée ~lock_guard() : mutex.unlock() à privilégier lock_guard() : mutex.lock()
  • 453.
    300 unique_lock plus général quelock_guard void fun(std::mutex& mutex) { if (auto lock = std::unique_lock<std::mutex>(mutex, std::try_to_lock)) { std::cout << ":-)" << std::endl; std::this_thread::sleep_for(std::chrono::seconds{2}); } else { std::cout << ":-(" << std::endl; } } std::mutex m; std::thread t0{fun, std::ref(m)}; std::thread t1{fun, std::ref(m)}; t0.join(); t1.join();
  • 454.
    300 unique_lock plus général quelock_guard void fun(std::mutex& mutex) { if (auto lock = std::unique_lock<std::mutex>(mutex, std::try_to_lock)) { std::cout << ":-)" << std::endl; std::this_thread::sleep_for(std::chrono::seconds{2}); } else { std::cout << ":-(" << std::endl; } } std::mutex m; std::thread t0{fun, std::ref(m)}; std::thread t1{fun, std::ref(m)}; t0.join(); t1.join(); tente de prendre le mutex ~unique_lock() : mutex.unlock()
  • 455.
    301 unique_lock avec un timed_mutex constauto lock = std::unique_lock<std::timed_mutex>{mutex, std::chrono::seconds{1}} if (lock) { std::cout << ":-)" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds{1500}); } else { std::cout << ":-(" << std::endl; }
  • 456.
    302 unique_lock peut-être transféré std::unique_lock<std::mutex> lock(std::mutex& mutex) { returnstd::unique_lock<std::mutex>(mutex); } void fun(std::mutex& mutex) { const auto l = lock(mutex); std::this_thread::sleep_for(std::chrono::milliseconds{1500}); std::cout << std::this_thread::get_id() << std::endl; }
  • 457.
    303 lock struct foo { std::mutex mutex; intdata; foo(int d) : mutex{}, data{d} {} }; void swap(foo& lhs, foo& rhs) noexcept { std::lock_guard<std::mutex> lhs_lock{lhs.mutex}; std::lock_guard<std::mutex> rhs_lock{rhs.mutex}; std::swap(lhs.data, rhs.data); } foo f1{1}, f2{2}; std::thread t0{[&]{swap(f1, f2);}}; std::thread t1{swap, std::ref(f2), std::ref(f1)}; verrouiller plusieurs mutex à la fois (1) quel est le problème?
  • 458.
    304 lock verrouiller plusieurs mutexà la fois (2) struct foo { std::mutex mutex; int data; foo(int d) : mutex{}, data{d} {} }; void swap(foo& lhs, foo& rhs) noexcept { std::lock(lhs.mutex, rhs.mutex); std::lock_guard<std::mutex> lhs_lock{lhs.mutex, std::adopt_lock}; std::lock_guard<std::mutex> rhs_lock{rhs.mutex, std::adopt_lock}; std::swap(lhs.data, rhs.data); } foo f1{1}, f2{2}; std::thread t0{[&]{swap(f1, f2);}}; std::thread t1{swap, std::ref(f2), std::ref(f1)};
  • 459.
    304 lock verrouiller plusieurs mutexà la fois (2) struct foo { std::mutex mutex; int data; foo(int d) : mutex{}, data{d} {} }; void swap(foo& lhs, foo& rhs) noexcept { std::lock(lhs.mutex, rhs.mutex); std::lock_guard<std::mutex> lhs_lock{lhs.mutex, std::adopt_lock}; std::lock_guard<std::mutex> rhs_lock{rhs.mutex, std::adopt_lock}; std::swap(lhs.data, rhs.data); } foo f1{1}, f2{2}; std::thread t0{[&]{swap(f1, f2);}}; std::thread t1{swap, std::ref(f2), std::ref(f1)}; garantit l’absence d’interblocage
  • 460.
    305 condition_variable attendre un événement std::mutexmutex; std::condition_variable cond; int data; bool produced = false; void producer() { std::unique_lock<std::mutex> l{mutex}; data = 0; produced = true; cond.notify_one(); } void consumer() { std::unique_lock<std::mutex> l{mutex}; cond.wait(l, []{return produced;}); ++data; }
  • 461.
    305 condition_variable attendre un événement std::mutexmutex; std::condition_variable cond; int data; bool produced = false; void producer() { std::unique_lock<std::mutex> l{mutex}; data = 0; produced = true; cond.notify_one(); } void consumer() { std::unique_lock<std::mutex> l{mutex}; cond.wait(l, []{return produced;}); ++data; } éviter les réveils intempestifs
  • 462.
    306 async #include <future> #include <chrono> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { std::async(std::launch::async, sleep, 1500); return 0; } lancer une tâche asynchrone *sauf avec Visual Studio 2013 : bug peut créer un nouveau thread ou réutiliser un existant
  • 463.
    306 async #include <future> #include <chrono> #include<thread> void sleep(std::size_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds{ms}); }; int main () { std::async(std::launch::async, sleep, 1500); return 0; } lancer une tâche asynchrone bloque tant que la tâche asynchrone n’est pas finie* *sauf avec Visual Studio 2013 : bug peut créer un nouveau thread ou réutiliser un existant
  • 464.
    307 future pour récupérer lerésultat d’une tâche asynchrone int worker() { return 42; }; auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get();
  • 465.
    307 future pour récupérer lerésultat d’une tâche asynchrone int worker() { return 42; }; auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get(); appel bloquant
  • 466.
    308 future plusieurs manières d’attendrele résultat (1) int worker(); auto future = std::async(std::launch::async, worker); future.wait(); const auto res = future.get();
  • 467.
    308 future plusieurs manières d’attendrele résultat (1) int worker(); auto future = std::async(std::launch::async, worker); future.wait(); const auto res = future.get(); appel bloquant
  • 468.
    309 future plusieurs manières d’attendrele résultat (2) void fun() { std::this_thread::sleep_for(std::chrono::seconds{2}); }; auto f = std::async(std::launch::async, fun); if (f.wait_for(std::chrono::seconds{1}) == std::future_status::ready) { std::cout << ":-)n"; } else { std::cout << ":-(n"; }
  • 469.
    310 future récupérer les exceptionsd’une tâche asynchrone int fun() { throw std::runtime_error(":-("); return 42; }; auto future = std::async(std::launch::async, fun); try { const auto res = future.get(); } catch (const std::exception& e) { std::cerr << e.what() << 'n'; }
  • 470.
    310 future récupérer les exceptionsd’une tâche asynchrone int fun() { throw std::runtime_error(":-("); return 42; }; auto future = std::async(std::launch::async, fun); try { const auto res = future.get(); } catch (const std::exception& e) { std::cerr << e.what() << 'n'; }
  • 471.
    311 future possède un étatinterne int worker(); auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get(); const auto res = future.get(); // error!
  • 472.
    311 future possède un étatinterne int worker(); auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get(); const auto res = future.get(); // error! non constant
  • 473.
    311 future possède un étatinterne int worker(); auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get(); const auto res = future.get(); // error! non constant get() modifie l’état de future
  • 474.
    311 future possède un étatinterne int worker(); auto future = std::async(std::launch::async, worker); // do something else... // now wait for the result of the asynchronous task const auto res = future.get(); const auto res = future.get(); // error! non constant get() modifie l’état de future résultat déjà consommé
  • 475.
    312 future exemple #include <algorithm> #include <future> #include<iostream> #include <numeric> #include <vector> template <typename InputIterator> int parallel_sum(InputIterator beg, InputIterator end) { const auto len = std::distance(beg, end); if(len < 1000) return std::accumulate(beg, end, 0); const auto mid = beg + len/2; auto future = std::async( std::launch::async , parallel_sum<InputIterator>, mid, end); const auto sum = parallel_sum(beg, mid); return sum + future.get(); } int main() { const auto v = std::vector<int>(10000, 1); std::cout << parallel_sum(begin(v), end(v)); }
  • 476.
    313 async retour sur async: évaluation paresseuse int worker() { return 42; }; auto future = std::async(std::launch::deferred, worker); // do something else... // now launch the task const auto res = future.get();
  • 477.
    313 async retour sur async: évaluation paresseuse int worker() { return 42; }; auto future = std::async(std::launch::deferred, worker); // do something else... // now launch the task const auto res = future.get(); évaluation paresseuse dans le thread appelant
  • 478.
    313 async retour sur async: évaluation paresseuse int worker() { return 42; }; auto future = std::async(std::launch::deferred, worker); // do something else... // now launch the task const auto res = future.get(); évaluation paresseuse dans le thread appelant effectue l’évaluation
  • 479.
    314 async vs appelde fonction int fun(int x, int y); void do_something_with_res(int){} const auto res = fun(1,3); do_something_with_res(res); auto future = std::async(fun, 1, 3); do_something_with_res(future.get());
  • 480.
    314 async vs appelde fonction int fun(int x, int y); void do_something_with_res(int){} const auto res = fun(1,3); do_something_with_res(res); auto future = std::async(fun, 1, 3); do_something_with_res(future.get()); bloquant
  • 481.
    314 async vs appelde fonction int fun(int x, int y); void do_something_with_res(int){} const auto res = fun(1,3); do_something_with_res(res); auto future = std::async(fun, 1, 3); do_something_with_res(future.get()); bloquant possiblement bloquant
  • 482.
    315 packaged_task associe un std::futureà std::function int incr(int x) {return x + 1;} auto task = std::packaged_task<int(int)>{incr}; auto future = task.get_future(); auto thread = std::thread{std::move(task), 0}; std::cout << future.get() << 'n'; thread.join(); plus de souplesses que std::async ex. : pool de threads
  • 483.
    316 promise pendant de future #include<future> #include <iostream> #include <thread> void fun(std::promise<int> promise) { promise.set_value(0); } int main() { auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; std::cout << future.get() << std::endl; thread.join(); }
  • 484.
    316 promise pendant de future #include<future> #include <iostream> #include <thread> void fun(std::promise<int> promise) { promise.set_value(0); } int main() { auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; std::cout << future.get() << std::endl; thread.join(); } non copiable
  • 485.
    317 promise producteur consommateur promise.set_value(0); future.get() auto promise= std::promise<int>{}; auto future = promise.get_future(); 1 2 3 4 cycle de vie
  • 486.
    318 promise ne rendre lerésultat disponible qu’à la fin de l’exécution #include <chrono> #include <future> #include <iostream> #include <thread> void fun(std::promise<int>&& promise) { promise.set_value_at_thread_exit(0); std::this_thread::sleep_for(std::chrono::seconds{1}); } int main() { auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; std::cout << future.get() << std::endl; thread.join(); }
  • 487.
    319 promise exceptions (1) void fun(std::promise<int>&&promise) { try { throw std::runtime_error{":-("}; } catch (...) { promise.set_exception(std::current_exception()); } } auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; try { std::cout << future.get() << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } thread.join();
  • 488.
    319 promise exceptions (1) void fun(std::promise<int>&&promise) { try { throw std::runtime_error{":-("}; } catch (...) { promise.set_exception(std::current_exception()); } } auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; try { std::cout << future.get() << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } thread.join(); positionne l’exception du future
  • 489.
    320 promise exceptions (2) void fun(std::promise<int>&&promise) { auto e = std::runtime_error{":-("}; promise.set_exception(std::make_exception_ptr(e)); } auto promise = std::promise<int>{}; auto future = promise.get_future(); auto thread = std::thread{fun, std::move(promise)}; try { std::cout << future.get() << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } thread.join(); autre manière…
  • 490.
    321 promise • En résumé ‣permet un contrôle fin sur l’obtention des résultats ‣ permet un contrôle fin sur les exceptions propagées • std::promise ‣ côté producteur du résultat • std::future ‣ côté consommateur du résultat
  • 491.
  • 492.
    323 ~future le destructeur d’unfuture est bloquant si créé par std::async int main() { auto task = std::packaged_task<void()>{fun}; auto future = task.get_future(); auto thread = std::thread{std::move(task)}; thread.detach(); } // exit immediately mais pas pour std::packaged_task et std::promise int main() { auto future = std::async(std::launch::async, incr); } // wait for async completion
  • 493.
    324 shared_future partager un std::futureentre plusieurs threads auto ready_promise = std::promise<void>{}; auto ready_future = ready_promise.get_future().share(); // or auto ready_future = std::shared_future<void>{ready_promise.get_future()}; auto t1 = std::thread([&]{ready_future.get();}); auto t2 = std::thread([&]{ready_future.get();}); ready_promise.set_value(); t1.join(); t2.join();
  • 494.
    324 shared_future partager un std::futureentre plusieurs threads auto ready_promise = std::promise<void>{}; auto ready_future = ready_promise.get_future().share(); // or auto ready_future = std::shared_future<void>{ready_promise.get_future()}; auto t1 = std::thread([&]{ready_future.get();}); auto t2 = std::thread([&]{ready_future.get();}); ready_promise.set_value(); t1.join(); t2.join(); plusieurs get() possibles sur le même future
  • 495.
  • 496.
    326 Héritage • Comment naviguerdans la hiérarchie • Héritage multiple • Dangers de l’héritage • Principe de substitution de Liskov • Des alternatives à l’héritage
  • 497.
    327 Naviguer dans lahiérarchie struct base { void fun1(){std::cout << "base::fun1n";} virtual void fun2(){std::cout << "base::fun2n";} }; struct derived : base { int i = 42; void fun2(){std::cout << "derived::fun2n";} void fun3(){std::cout << "derived::fun3 " << i << "n";} }; struct derived2 : base { void fun2(){std::cout << "derived2::fun2n";} void fun4(){std::cout << "derived::fun4n";} }; base* ptr = new derived; if (const auto as_derived = dynamic_cast<derived*>(ptr)) as_derived->fun3(); // OK base* ptr = new derived2; auto as_derived = static_cast<derived*>(ptr); as_derived->fun3(); // Oops! vérification à l’exécution vérification à la compilation downcast
  • 498.
    328 Naviguer dans lahiérarchie dynamic_cast vs typeid struct base{virtual ~base(){}}; struct d1 : base {}; struct d2 : d1 {}; base* ptr = new d2; if (auto* as_d2 = dynamic_cast<d2*>(ptr)) { // ... } if (typeid(*ptr) == typeid(d2)) { auto* as_d2 = static_cast<d2*>(ptr); // ... } typeid plus rapide que dynamic_cast if (typeid(*ptr) == typeid(d1)) { // typeid returns the dynamic type // thus typeid(*ptr) != typeid(d1) auto* as_d1 = static_cast<d1*>(ptr); } if (auto* as_d1 = dynamic_cast<d1*>(ptr)) { // OK } mais…
  • 499.
    329 Naviguer dans lahiérarchie en présence de shared_ptr struct base { void fun1(){std::cout << "base::fun1n";} virtual void fun2(){std::cout << "base::fun2n";} }; struct derived : base { int i = 42; void fun2(){std::cout << "derived::fun2n";} void fun3(){std::cout << "derived::fun3 " << i << "n";} }; struct derived2 : base { void fun2(){std::cout << "derived2::fun2n";} void fun4(){std::cout << "derived::fun3n";} }; auto ptr = std::shared_ptr<base>(new derived); if (const auto as_derived = std::dynamic_pointer_cast<derived>(ptr)) as_derived->fun3(); auto ptr = std::shared_ptr<base>(new derived2); auto as_derived = std::static_pointer_cast<derived>(ptr); as_derived->fun3(); // Oops!
  • 500.
    330 Prévoir l’héritage struct foo { foo(){std::cout<< "foo()n";} ~foo(){std::cout << "~foo()n";} }; struct base { ~base() {std::cout << "~base()n";} }; struct derived : base { foo f; ~derived() {std::cout << "~derived()n";} }; base* ptr = new derived; delete ptr; // Undefined behavior foo() ~base()en général : destructeur non virtuel destructeur virtuel effacement à travers le type de base
  • 501.
    331 Prévoir l’héritage struct base { protected: ~base(){std::cout << "~base()n";} }; struct derived : base { ~derived() {std::cout << "~derived()n";} }; base* b = new derived; delete b; // compilation error derived* d = new derived; delete d; empêcher l’effacement à travers le type de base impossible sur le type de base ok sur le type concret
  • 502.
    332 Prévoir l’héritage pour uneclasse destinée à être héritée, le destructeur doit-être : public & virtual protected & non-virtual ou
  • 503.
    333 Prévoir l’héritage struct basefinal { }; struct derived : base { }; rappel C++11 : interdire l’héritage error: base 'base' is marked 'final'
  • 504.
    334 Héritage multiple problème du“diamant” struct A { void fun(){} }; struct B : A {}; struct C : A {}; struct D : B, C {}; auto d = D{}; d.fun(); // which fun is it?erreur
  • 505.
    335 Héritage multiple problème du“diamant” A fun() B C D
  • 506.
    335 Héritage multiple problème du“diamant” A fun() B C D A fun()
  • 507.
    336 Héritage virtuel éviter leproblème du “diamant” struct A { void fun(){} }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C {}; auto d = D{}; d.fun(); // OK!
  • 508.
    337 Dangers de l’héritage •Couplage fort ‣ relation de couplage la plus forte après friend ‣ “fragile base class” • Possible impact sur les invariants • Slicing
  • 509.
    338 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { data.insert(end(data), begin(v), end(v)); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 3
  • 510.
    338 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { data.insert(end(data), begin(v), end(v)); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 3 1
  • 511.
    338 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { data.insert(end(data), begin(v), end(v)); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 3 1 2
  • 512.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!!
  • 513.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1
  • 514.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1 2
  • 515.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1 2 3
  • 516.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1 2 3 4
  • 517.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1 2 3 4 le code client est cassé par la mise à jour de la classe de base!
  • 518.
    339 Héritage : couplagefort struct values { std::vector<int> data; ~values() {} virtual void add(const std::vector<int>& v) { // data.insert(end(data), begin(v), end(v)); for (auto x : v) add(x); } virtual void add(int v) { data.push_back(v); } }; struct derived_values final : values { std::size_t counter = 0; void add(const std::vector<int>& v) override { counter += v.size(); values::add(v); } void add(int v) override { counter += 1; values::add(v); } }; auto ptr = std::unique_ptr<values>(new derived_values); ptr->add({1,2,3}); std::cout << static_cast<const derived_values&>(*ptr).counter; // 6!!! 1 2 3 4 le code client est cassé par la mise à jour de la classe de base! “fragile base class”
  • 519.
    340 Héritage : invariants structlist { std::list<int> data; void push_front(int x) {data.push_front(x);} void pop_front() {data.pop_front();} void push_back(int x) {data.push_back(x);} void pop_back() {data.pop_back();} }; struct stack : list { void push(int x) {list::push_back(x);} void pop() {list::pop_back();} }; auto s = stack{}; s.push(3); s.push(4); s.pop(); s.push_front(4); // !!!!
  • 520.
    340 Héritage : invariants structlist { std::list<int> data; void push_front(int x) {data.push_front(x);} void pop_front() {data.pop_front();} void push_back(int x) {data.push_back(x);} void pop_back() {data.pop_back();} }; struct stack : list { void push(int x) {list::push_back(x);} void pop() {list::pop_back();} }; auto s = stack{}; s.push(3); s.push(4); s.pop(); s.push_front(4); // !!!! casse les invariants
  • 521.
    340 Héritage : invariants structlist { std::list<int> data; void push_front(int x) {data.push_front(x);} void pop_front() {data.pop_front();} void push_back(int x) {data.push_back(x);} void pop_back() {data.pop_back();} }; struct stack : list { void push(int x) {list::push_back(x);} void pop() {list::pop_back();} }; auto s = stack{}; s.push(3); s.push(4); s.pop(); s.push_front(4); // !!!! casse les invariants private
  • 522.
    341 Slicing attention au passagepar valeur de types polymorphes struct base { virtual void fun() const {std::cout << "base::fun()";} }; struct derived : base { void fun() const {std::cout << "derived::fun()";} }; void slice(const base ptr) { ptr.fun(); } void no_slice(const base& ptr) { ptr.fun(); } const auto d = derived{}; slice(d); // base::fun() no_slice(d); // derived::fun()
  • 523.
    341 Slicing attention au passagepar valeur de types polymorphes struct base { virtual void fun() const {std::cout << "base::fun()";} }; struct derived : base { void fun() const {std::cout << "derived::fun()";} }; void slice(const base ptr) { ptr.fun(); } void no_slice(const base& ptr) { ptr.fun(); } const auto d = derived{}; slice(d); // base::fun() no_slice(d); // derived::fun() & manquant
  • 524.
    342 Principe de substitutionde Liskov struct base { virtual void fun1() = 0; virtual void fun2() = 0; }; struct liskov : base { void fun1() {} void fun2() {} private: void impl() {} }; struct non_liskov : base { void fun1() {} void fun2() {} void fun3() {} // new behavior }; base* ptr = new non_liskov; if (auto p = dynamic_cast<non_liskov*>(ptr)) p->fun3();
  • 525.
    342 Principe de substitutionde Liskov struct base { virtual void fun1() = 0; virtual void fun2() = 0; }; struct liskov : base { void fun1() {} void fun2() {} private: void impl() {} }; struct non_liskov : base { void fun1() {} void fun2() {} void fun3() {} // new behavior }; contrat, interface implémentation ne peut plus être substituée à une autre implémentation base* ptr = new non_liskov; if (auto p = dynamic_cast<non_liskov*>(ptr)) p->fun3();
  • 526.
    343 But de l’héritage •Que cherche-t-on à faire? • Réutiliser du code? ‣ pour factoriser • Fournir une interface commune (contrat)? ‣ pour cacher l’implémentation • Agréger des types disjoints? ‣ ex. : feuilles et nœuds d’un arbre
  • 527.
    344 “Est-ce que jeveux pouvoir substituer mon implémentation à une autre?”
  • 528.
    345 Des alternatives àl’héritage • Composition • Union discriminée • Héritage statique
  • 529.
    Composition 346 • Contenir uneimplémentation plutôt qu’en hériter ‣ une classe est la somme de comportements ‣ ex : contenir un objet de typeTCP ou UDP ‣ si une classe hérite deTCP, alors cela veut dire qu’elle peut se substituer àTCP; est-ce vraiment cela que l’on souhaite? • Permet de changer à la volée d’implémentation ‣ ex : auto f = foo{udp{}}; f.network() = tcp{};
  • 530.
    Composition 347 // worker USESany kind of network struct worker { std::shared_ptr<network> net_; worker(const std::shared_ptr<network>&); std::shared_ptr<network>& network(); void operator()() const; }; auto x = worker{}; // we can invoke write on x; // is this what we really want? x.write(); // user can store worker as a network network& n = i; // but operator()() is lost n(); // error // a downcast is needed dynamic_cast<worker&>(n).operator()(); auto t = std::make_shared<network>(tcp{}); auto x = worker{n}; // good, we can't invoke write() on x x.write() // error // we can change implementation on-the-fly auto u = std::make_shared<network>(udp{}); x.network() = u; // worker IS a tcp struct worker : tcp { void operator()() const; }; struct network{virtual void write() = 0;}; struct tcp : network; struct udp : network; héritage composition
  • 531.
    Union discriminée 348 struct foo{ enum class ty {int_ty, tab_ty, double_ty} type; union { int i; int tab[10]; double d; } u; }; void print(const foo& f) { switch (f.type) { case foo::ty::int_ty : std::cout << "int: " << f.u.i << 'n';break; case foo::ty::tab_ty : std::cout << "tab: " << f.u.tab[0] << 'n'; break; case foo::ty::double_ty : std::cout << "double : " << f.u.d << 'n'; break; } } auto f = foo{}; f.type = foo::ty::int_ty; f.u.i = 32; print(f); // int: 32 f.type = foo::ty::double_ty; f.u.d = 42.42; print(f); // double: 42.42 facile avec les types primitifs
  • 532.
    Union discriminée 349 généralisation avecBoost.Variant struct foo{int x;}; struct bar {double x; double y;}; struct visitor { using result_type = void; result_type operator()(const foo& f) const { std::cout << "Foo " << f.x << ‘n'; } result_type operator()(const bar& b) const { std::cout << "Bar " << b.x << " " << b.y << ‘n'; } }; using variant = boost::variant<foo, bar>; const auto v0 = variant{foo{42}}; const auto v1 = variant{bar{1.0, 2.0}}; boost::apply_visitor(visitor{}, v0); boost::apply_visitor(visitor{}, v1);
  • 533.
    Héritage statique 350 utilisation duCRTP template <typename Derived> struct base { void common() const {std::cout << "common";} void fun() const {static_cast<Derived*>(this)->fun_impl();} }; struct derived_1 : public base<derived_1> { void fun_impl() const {std::cout << "derived_1::fun";} }; struct derived_2 : public base<derived_2> { void fun_impl() const {std::cout << "derived_2::fun";} }; const auto d1 = derived_1{}; const auto d2 = derived_2{}; d1.common(); // “common” d2.common(); // “common” d1.fun(); // “derived_1::fun” d2.fun(); // “derived_2::fun”
  • 534.
    351 Quelques techniques • Injectionde dépendances • Interfaces non-virtuelles • RAII • Mixins • Traits • Type erasure • PIMPL
  • 535.
    352 Injection de dépendances structoutput {virtual void write(const std::string&) = 0;}; struct network : output { network(const std::string& ip, unsigned int port); virtual void write(const std::string&) override; }; struct file : output { file(const std::string& path); virtual void write(const std::string&) override; }; struct logger { std::unique_ptr<output> out; logger(const std::string& ip, unsigned int port); logger(const std::string& path); void operator()(const std::string& str); }; auto lnet = logger{"192.168.0.1", 4242}; auto lfile = logger{"/path/to"};
  • 536.
    352 Injection de dépendances structoutput {virtual void write(const std::string&) = 0;}; struct network : output { network(const std::string& ip, unsigned int port); virtual void write(const std::string&) override; }; struct file : output { file(const std::string& path); virtual void write(const std::string&) override; }; struct logger { std::unique_ptr<output> out; logger(const std::string& ip, unsigned int port); logger(const std::string& path); void operator()(const std::string& str); }; comment tester? auto lnet = logger{"192.168.0.1", 4242}; auto lfile = logger{"/path/to"};
  • 537.
    352 Injection de dépendances structoutput {virtual void write(const std::string&) = 0;}; struct network : output { network(const std::string& ip, unsigned int port); virtual void write(const std::string&) override; }; struct file : output { file(const std::string& path); virtual void write(const std::string&) override; }; struct logger { std::unique_ptr<output> out; logger(const std::string& ip, unsigned int port); logger(const std::string& path); void operator()(const std::string& str); }; comment tester? et si de nouveaux backends sont disponibles? avec les mêmes constructeurs? auto lnet = logger{"192.168.0.1", 4242}; auto lfile = logger{"/path/to"};
  • 538.
    353 Injection de dépendances structlogger { std::unique_ptr<output> out; logger(std::unique_ptr<output>&&); void operator()(const std::string& str); }; il suffit d’externaliser la construction des dépendances auto impl = std::unique_ptr<output>{new file{"/path/to"}}; auto l = logger{std::move(fileimpl)}; pour tester, il suffit de créer un mock de output
  • 539.
    353 Injection de dépendances structlogger { std::unique_ptr<output> out; logger(std::unique_ptr<output>&&); void operator()(const std::string& str); }; il suffit d’externaliser la construction des dépendances auto impl = std::unique_ptr<output>{new file{"/path/to"}}; auto l = logger{std::move(fileimpl)}; pour tester, il suffit de créer un mock de output “injection de dépendances”
  • 540.
    354 Interfaces non-virtuelles class interface { public: ~interface(); voidprocess() { // some pre stuff here process_impl(); // some post stuff here } private: // default implementation // could be =0 virtual void process_impl(); }; class impl1 : public interface { virtual void process_impl(); }; class impl2 : public interface { virtual void process_impl(); };
  • 541.
    354 Interfaces non-virtuelles class interface { public: ~interface(); voidprocess() { // some pre stuff here process_impl(); // some post stuff here } private: // default implementation // could be =0 virtual void process_impl(); }; class impl1 : public interface { virtual void process_impl(); }; class impl2 : public interface { virtual void process_impl(); }; permet de s’assurer que des pre-et post traitements sont toujours effectués peut fournir une implémentation par défaut implémentation privée, ne peut être appelée par le code client
  • 542.
    355 Interfaces non-virtuelles class xml: public base { void read_impl (istream&); void write_impl (ostream&); }; class json : public base { void read_impl (istream&); void write_impl (ostream&); }; class text : public base { void read_impl (istream&); void write_impl (ostream&); }; class base { public: void read(istream& i) { lock_guard<mutex> lock{mutex_}; read_impl(i); } void write(ostream& o) { lock_guard<mutex> lock{mutex_}; write_impl(o); } virtual ~base() {} private: mutex mutex_; some_data_to_protect data_; virtual void read_impl(istream&) = 0; virtual void write_impl(ostream&)= 0; }; exemple : sérialisation d’une structure à protéger
  • 543.
    356 RAII (Resource Acquisitionis Initialization) • Utiliser la sortie de portée et les destructeurs • Bibliothèque standard ‣ unique_ptr ‣ shared_ptr ‣ lock_guard ‣ thread • Code plus résistant ‣ aux exceptions ‣ aux oublis de fermetures de ressources • Automatiser des tâches
  • 544.
    357 RAII (Resource Acquisitionis Initialization) #include <cstdio> #include <string> struct file { std::string path; std::FILE* fp; file(const std::string p) : path{p}, fp{std::fopen(p.c_str(), "w+")} {} ~file() {std::fclose(fp);} void write(const std::string& str) {std::fputs(str.c_str(), fp);} }; int main() { auto f = file{"/Users/hal/Desktop/foo.txt"}; f.write("abc"); } // file is automatically closed fermeture automatique de ressource
  • 545.
    358 RAII (Resource Acquisitionis Initialization) struct timer { std::chrono::time_point<std::chrono::steady_clock> start; timer() : start(std::chrono::steady_clock::now()) // start timer {} ~timer() // display duration on destruction { const auto end = std::chrono::steady_clock::now(); const auto duration = std::chrono::duration<double, std::milli>{end - start}; std::cout << duration.count() << "msn"; } }; { const auto _ = timer{}; const auto vec = std::vector<int>(10000); } // ~timer(), displays something like 0.111545ms { const auto _ = timer{}; const auto string = std::string{"abc"}; } // ~timer(), displays something like 0.001886ms automatiser des tâches
  • 546.
    358 RAII (Resource Acquisitionis Initialization) struct timer { std::chrono::time_point<std::chrono::steady_clock> start; timer() : start(std::chrono::steady_clock::now()) // start timer {} ~timer() // display duration on destruction { const auto end = std::chrono::steady_clock::now(); const auto duration = std::chrono::duration<double, std::milli>{end - start}; std::cout << duration.count() << "msn"; } }; { const auto _ = timer{}; const auto vec = std::vector<int>(10000); } // ~timer(), displays something like 0.111545ms { const auto _ = timer{}; const auto string = std::string{"abc"}; } // ~timer(), displays something like 0.001886ms automatiser des tâches ne pas oublier d’utiliser une variable, sinon le destructeur est appelé immédiatement
  • 547.
    359 Mixins fragments de classesdestinés à enrichir une classe template<class T> struct mixin1 : public T { void f() {std::cout << "mixin1::f()n"; T::f();} }; template<class T> struct mixin2 : public T { void g() {std::cout << "mixin2::g()n"; T::f();} }; struct foo { void f() {std::cout << "foo::f()n";}; }; // compose a new type, mixins act as decorators using enhanced_type = mixin2<mixin1<foo>>; auto x = enhanced_type{}; x.g();
  • 548.
    360 Mixins en utilisant leCRTP template<class T> struct mixin1 { void fun1() {/* new behaviour */} }; template<class T> struct mixin2 { void fun2() { // decorate an existing behaviour static_cast<T*>(this)->fun(); } }; // mixins are used as building blocks struct foo : mixin1<foo>, mixin2<foo> { void fun() {std::cout << "foo::fun()n";}; }; auto f = foo{}; f.fun1(); f.fun2(); f.fun();
  • 549.
    361 Mixins conserver une interfacecommune struct base { virtual ~base() {} virtual std::unique_ptr<base> clone() const = 0; }; template <typename Derived> struct base_crtp : public base { std::unique_ptr<base> clone() const override { const auto& as_derived = static_cast<const Derived&>(*this); return std::unique_ptr<base>{new Derived{as_derived}}; } }; struct derived1 : public base_crtp<derived1> {}; struct derived2 : public base_crtp<derived2> {}; const auto ptr = std::unique_ptr<base>{new derived1}; const auto ptr2 = ptr->clone();
  • 550.
    361 Mixins conserver une interfacecommune struct base { virtual ~base() {} virtual std::unique_ptr<base> clone() const = 0; }; template <typename Derived> struct base_crtp : public base { std::unique_ptr<base> clone() const override { const auto& as_derived = static_cast<const Derived&>(*this); return std::unique_ptr<base>{new Derived{as_derived}}; } }; struct derived1 : public base_crtp<derived1> {}; struct derived2 : public base_crtp<derived2> {}; const auto ptr = std::unique_ptr<base>{new derived1}; const auto ptr2 = ptr->clone(); héritent de base interface commune mixin
  • 551.
    362 Traits template <typename T> structhas_feature { static const auto value = false; }; template <> struct has_feature<std::vector<int>> { static const auto value = true; }; template <bool HasFeature, typename T> struct algo_impl; template <typename T> struct algo_impl<false, T> { void operator()(const T&) const {std::cout << "O(n)n";} }; template <typename T> struct algo_impl<true, T> { void operator()(const T&) const {std::cout << "O(1)n";} }; template <typename T> void algo(const T& x) { algo_impl<has_feature<T>::value, T>{}(x); } algo(std::vector<int>{}); algo(std::vector<long>{}); tirer parti de caractéristiques à la compilation
  • 552.
    362 Traits template <typename T> structhas_feature { static const auto value = false; }; template <> struct has_feature<std::vector<int>> { static const auto value = true; }; template <bool HasFeature, typename T> struct algo_impl; template <typename T> struct algo_impl<false, T> { void operator()(const T&) const {std::cout << "O(n)n";} }; template <typename T> struct algo_impl<true, T> { void operator()(const T&) const {std::cout << "O(1)n";} }; template <typename T> void algo(const T& x) { algo_impl<has_feature<T>::value, T>{}(x); } algo(std::vector<int>{}); algo(std::vector<long>{}); trait général trait spécialisé algorithme général algorithme spécialisé dispatch statique tirer parti de caractéristiques à la compilation
  • 553.
    363 Traits struct tag_1{}; struct tag_2{}; structtag_3{}; template <typename T> struct traits { using category = tag_1; }; template <> struct traits<std::vector<int>> { using category = tag_2; }; template <typename T> void algo_impl(const T& x, tag_1) { std::cout << "tag1n"; } template <typename T> void algo_impl(const T& x, tag_2) { std::cout << "tag2n"; } template <typename T> void algo_impl(const T& x, tag_3) { std::cout << "tag3n"; } template <typename T> void algo(const T& x) { algo_impl(x, typename traits<T>::category{}); } tag dispatching
  • 554.
    363 Traits struct tag_1{}; struct tag_2{}; structtag_3{}; template <typename T> struct traits { using category = tag_1; }; template <> struct traits<std::vector<int>> { using category = tag_2; }; template <typename T> void algo_impl(const T& x, tag_1) { std::cout << "tag1n"; } template <typename T> void algo_impl(const T& x, tag_2) { std::cout << "tag2n"; } template <typename T> void algo_impl(const T& x, tag_3) { std::cout << "tag3n"; } template <typename T> void algo(const T& x) { algo_impl(x, typename traits<T>::category{}); } tag dispatching propriétés spécialisation spécialisation spécialisation trait par défaut trait spécialisé dispatch
  • 555.
    364 Type erasure struct foo { voidfun1(int& x) { x += 1; } int fun2(int x) { return x * 2; } }; struct bar { void fun1(int& x) { x += 2; } int fun2(int x) { return x * 4; } }; scénario : deux classes avec la même interface on aimerait les stocker dans le même conteneur sans toucher aux classes (donc pas d’héritage) auto vec = std::vector<???>{}; vec.emplace_back(foo{}); vec.emplace_back(bar{});
  • 556.
    365 Type erasure struct concept { virtual~concept() {} virtual void fun1(int&) = 0; virtual int fun2(int) = 0; }; template <typename T> struct model final : public concept { T impl; model(const T& x) : impl(x) {} void fun1(int& x) override { impl.fun1(x); } int fun2(int x) override { return impl.fun2(x); } }; struct object { // type erasure std::shared_ptr<concept> ptr; template <typename T> object(const T& x) : ptr{std::make_shared<model<T>>(x)} {} void fun1(int& x) { ptr->fun1(x); } int fun2(int x) { return ptr->fun2(x); } }; une solution : effacer leur type
  • 557.
    366 Type erasure auto vec= std::vector<object>{}; vec.emplace_back(object{foo{}}); vec.emplace_back(object{bar{}}); auto o = object{foo{}}; std::cout << o.fun2(4) << 'n'; utilisation
  • 558.
    367 Pointer to Implementation- PIMPL réduire les dépendances à la compilation class foo { public: void fun1(int&); private: // implementation detail data_type data; }; main.cc #include foo.cc #include
  • 559.
    368 Pointer to Implementation- PIMPL réduire les dépendances à la compilation // forward declaration class foo_impl; class foo { public: void fun1(int&); private: std::shared_ptr<foo_impl> pimpl; }; main.cc #include class foo_impl { public: void fun1(int&); private: // implementation detail data_type data; }; foo.cc #include
  • 560.
    Quelques lectures 369 • Atour of C++ - Bjarne Stroustrup • Effective C++, Effective STL, Effective Modern C++ - Scott Meyers • The C++ Standard Library - Nicolai Josuttis • C++ Concurrency in Action - Anthony Williams • Modern C++ Design - Andrei Alexandrescu