1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
|
---
language: c++
filename: learncpp-fr.cpp
contributors:
- ["Steven Basart", "http://github.com/xksteven"]
- ["Matt Kline", "https://github.com/mrkline"]
- ["Geoff Liu", "http://geoffliu.me"]
- ["Connor Waters", "http://github.com/connorwaters"]
translators:
- ["Xuan-thi Nguyen", "http://github.com/mellenguyen"]
lang: fr-fr
---
C++ est un langage de programmation système qui,
[selon son créateur Bjarne Stroustrup](http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote),
fut créé pour
- être un "C amélioré"
- gérer l'abstraction des données
- gérer la programmation orienté objet
- gérer la programmation générique
Bien que sa syntaxe puisse être plus difficile ou complexe que des langages
récents, il est largement utilisé car il compile en instructions natives qui
peuvent être directement exécutées par le processeur et offre un contrôle
rigoureux du matériel (comme le C) tout en fournissant des caractéristiques de
haut niveau telles que la généricité, les exceptions et les classes.
Cette combinaison de vitesse et de fonctionnalités rend le C++ un des langages
de programmation les plus utilisés au monde.
```c++
/////////////////////////////////
// Comparaison avec le C
/////////////////////////////////
// C++ est _presque_ un sur-ensemble du C et partage sa syntaxe basique pour les
// déclarations de variables, les types primitifs et les fonctions.
// Tout comme en C, le point d'entrée de votre programme est une fonction
// appelée main, avec un integer comme type de retour.
// Cette valeur constitue l'état de fin d'exécution du programme.
// Voir http://en.wikipedia.org/wiki/Exit_status pour plus d'informations.
int main(int argc, char** argv)
{
// Les arguments de ligne de commande sont passés avec argc et argv de la
// même manière qu'en C.
// argc indique le nombre d'arguments,
// et argv est un tableau de chaînes façon C (char*)
// représentant les arguments.
// Le premier argument est le nom par lequel le programme est appelé.
// argc et argv peuvent être omis si vous ne vous souciez pas des
// arguments, nous donnant comme signature de fonction int main()
// Un état de fin d'exécution 0 indique le succès.
return 0;
}
// Cependant, C++ varie du C selon certains éléments:
// En C++, les caractères littéraux sont des chars
sizeof('c') == sizeof(char) == 1
// En C, les caractères littéraux sont des ints
sizeof('c') == sizeof(int)
// C++ a un prototypage strict
void func(); // fonction qui ne prend aucun argument
// En C
void func(); // fonction qui peut prendre n'importe quel nombre d'arguments
// Utilise nullptr au lieu de NULL in C++
int* ip = nullptr;
// Les en-têtes standards du C sont disponibles en C++,
// mais son préfixés avec "c" et n'ont pas de suffixe .h
#include <cstdio>
int main()
{
printf("Bonjour tout le monde!\n");
return 0;
}
/////////////////////////////////
// Surchage de fonctions
/////////////////////////////////
// C++ gère la surchage de fonctions
// Chaque fonction fournie prend différents paramètres.
void print(char const* maChaine)
{
printf("Chaîne %s\n", maChaine);
}
void print(int monEntier)
{
printf("Mon entier est %d", monEntier);
}
int main()
{
print("Bonjour"); // Utilise void print(const char*)
print(15); // Utilise void print(int)
}
/////////////////////////////////////////////
// Arguments par défaut de fonctions
/////////////////////////////////////////////
// Vous pouvez fournir des arguments par défaut pour une fonction s'ils ne sont
// pas fournis par l'appelant.
void faitDesChosesAvecDesEntiers(int a = 1, int b = 4)
{
// Do something with the ints here
}
int main()
{
faitDesChosesAvecDesEntiers(); // a = 1, b = 4
faitDesChosesAvecDesEntiers(20); // a = 20, b = 4
faitDesChosesAvecDesEntiers(20, 5); // a = 20, b = 5
}
// Les arguments par défaut doivent être à la fin de la liste des arguments.
void invalidDeclaration(int a = 1, int b) // Erreur !
{
}
//////////////////////////
// Espaces de nom
//////////////////////////
// Les espaces de nom fournissent une séparation des portées pour les
// variables, fonctions, et autres déclarations.
// Les espaces de nom peuvent être imbriqués.
namespace Premier {
namespace Imbrique {
void foo()
{
printf("Ceci est le Premier::Imbrique::foo\n");
}
} // fin de l'espace de nom Imbrique
} // fin de l'espace de nom Premier
namespace Second {
void foo()
{
printf("Ceci est le Second::foo\n")
}
}
void foo()
{
printf("Ceci est un foo global\n");
}
int main()
{
// Inclut tous les symboles de l'espace de nom Second dans la portée
// actuelle. Notez que le foo() simple ne marche plus, car l'appel est
// ambigu entre le foo de l'espace de nom Second et celui de premier
// niveau.
using namespace Second;
Second::foo(); // imprime "Ceci est le Second::foo"
Premier::Imbrique::foo(); // imprime "Ceci est le Premier::Imbrique::foo"
::foo(); // imprime "Ceci est un foo global"
}
/////////////////////////
// Entrée/Sortie
/////////////////////////
// Les entrées et sorties en C++ utilisent des flux (streams)
// cin, cout et cerr représentent stdin, stdout et stderr.
// << est l'opérateur d'insertion et >> est l'opérateur d'extraction.
#include <iostream> // Inclusion pour les flux d'entrée/sortie
// Les flux sont dans l'espace de nom std (librairie standard)
using namespace std;
int main()
{
int monEntier;
// Affiche sur stdout (ou le terminal/l'écran)
cout << "Entrez votre chiffre favori:\n";
// Prend l'entrée clavier
cin >> monEntier;
// cout peut également être formaté
cout << "Votre chiffre favori est " << monEntier << "\n";
// imprime "Votre chiffre favori est <monEntier>"
cerr << "Utilisé pour les messages d'erreurs";
}
/////////////////////////////////
// Chaînes de caractères
/////////////////////////////////
// Les chaînes de caractères en C++ sont des objets et ont plusieurs fonctions
// membres
#include <string>
// Les chaînes de caractères sont aussi dans l'espace de
// nom std (librairie standard)
using namespace std;
string maChaine = "Bonjour";
string monAutreChaine = " tout le monde !";
// + est utilisé pour la concaténation.
cout << maChaine + monAutreChaine; // Bonjour tout le monde !"
cout << maChaine + " toi !"; // "Bonjour toi !"
// Les chaînes de caractères C++ sont mutables.
maChaine.append(" le chien !");
cout << maChaine; // "Bonjour le chien !"
//////////////////////
// Références
//////////////////////
// En plus des pointeurs comme ceux en C,
// C++ possède des _références_.
// Ce sont des types de pointeurs qui ne peuvent pas être réassignés
// une fois initialisés, et ne peuvent pas être nulles.
// Ils partagent la même syntaxe que les variables elles-mêmes:
// les * ne sont pas nécessaires pour les déréférencer et
// & (addresse de) n'est pas utilisé pour l'assignement.
using namespace std;
string foo = "Je suis foo";
string bar = "Je suis bar";
string& fooRef = foo; // Ceci créé une référence à foo
fooRef += ". Salut!"; // Modifie foo à travers la référence
cout << fooRef; // Affiche "Je suis foo. Salut!"
// Ne réassigne pas "fooRef". Ceci revient à faire "foo = bar", et
// foo == "I am bar"
// après cette ligne.
cout << &fooRef << endl; // Affiche l'adresse de foo
fooRef = bar;
cout << &fooRef << endl; // Affiche toujours l'adresse de foo
cout << fooRef; // Affiche "Je suis bar"
// L'adresse de fooRef reste la même, c.-à-d. référence toujours foo.
const string& barRef = bar; // Créé une référence constante de bar.
// Comme en C, les valeurs constantes (et pointeurs et références) ne peuvent
// être modifiées.
// Erreur, les valeurs constantes ne peuvent être modifiées.
barRef += ". Salut!";
// Parenthèse: avant de développer le sujet des références, nous devons
// introduire un concept appelé un objet temporaire. Supposons que nous ayons
// le code suivant :
string objetTemporaireFun() { ... }
string valeurRetenu = objetTemporaireFun();
// Les différents événements se déroulant à la seconde ligne sont :
// - un objet chaîne de caractères est retourné de objetTemporaireFun
// - une nouvelle chaîne de caractères est construite avec la valeur
// retournée comme argument du constructeur
// - l'objet retourné est détruit.
// L'objet retourné est appelé un objet temporaire. Les objets temporaires sont
// créés chaque fois qu'une fonction retourne un objet, et sont détruits à la
// fin de l'évaluation de l'expression fermante (c'est ce que le standard
// énonce, mais les compilateurs sont autorisés à changer ce comportement.
// Cherchez "optimisation valeur de retour" si vous êtes intéressé par ce genre
// de détails).
// Dans cette ligne de code :
foo(bar(objetTemporaireFun()))
// en supposant que foo et bar existent, l'objet retourné de objetTemporaireFun
// est passé à bar, et est détruit avant que foo soit appelé.
// Revenons maintenant aux références. L'exception à la règle "objet détruit à
// la fin de l'expression fermante" s'applique dans le cas d'un objet
// temporaire lié à une référence constante, où sa durée de vie se voit
// prolongée à la portée courante :
void referenceConstanteObjetTemporaireFun() {
// referenceConst prend l'objet temporaire, et est valide jusqu'à la fin de
// la fonction.
const string& referenceConst = objetTemporaireFun();
...
}
// Un autre type de référence introduit en C++11 est spécifiquement pour les
// objets temporaires. Vous ne pouvez pas avoir de variable de ce type, mais
// il prime dans la résolution de surcharge :
void fonctionFun(string& s) { ... } // Référence régulière
void fonctionFun(string&& s) { ... } // Référence un objet temporaire
string foo;
// Appelle la version avec référence régulière
fonctionFun(foo);
// Appelle la version avec référence temporaire
fonctionFun(objetTemporaireFun());
// Par exemple, vous aurez ces deux versions de constructeurs pour
// std::basic_string :
basic_string(const basic_string& other);
basic_string(basic_string&& other);
// L'idéal étant de construire une nouvelle chaîne de caractères avec un objet
// temporaire (qui sera détruit de toute façon), nous pouvons ainsi avoir un
// constructeur qui "sauve" des parties de cette chaîne de caractères
// temporaire. Vous verrez ce concept sous le nom de "sémantique de mouvement".
////////////////////////
// Enumérations
////////////////////////
// Les énumérations sont un moyen d'assigner une valeur à une constante
// fréquemment utilisée pour une meilleure visualisation et lecture du code.
enum ETypesDeVoitures
{
Berline,
Hayon,
4x4,
Break
};
ETypesDeVoitures ObtenirVoiturePreferee()
{
return ETypesDeVoitures::Hayon;
}
// En C++11, il existe une manière simple d'assigner un type à une énumération,
// ce qui peut-être utile en sérialisation de données et conversion
// d'énumérations entre le type voulu et ses constantes respectives.
enum ETypesDeVoitures : uint8_t
{
Berline, // 0
Hayon, // 1
4x4 = 254, // 254
Hybride // 255
};
void EcrireOctetDansLeFichier(uint8_t ValeurEntree)
{
// Sérialise la valeur d'entrée dans un fichier
}
void EcrireTypeVoiturePrefereDansLeFichier(ETypesDeVoitures TypeVoitureEntree)
{
// L'énumération est implicitement convertie en uint8_t du à la déclaration
// de son type d'énumération
EcrireOctetDansLeFichier(TypeVoitureEntree);
}
// D'autre part, vous pourriez ne pas vouloir que des énumérations soient
// accidentellement converties en entiers ou en d'autres énumérations. Il est
// donc possible de créer une classe d'énumération qui ne sera pas
// implicitement convertie.
enum class ETypesDeVoitures : uint8_t
{
Berline, // 0
Hayon, // 1
4x4 = 254, // 254
Hybride // 255
};
void EcrireOctetDansLeFichier(uint8_t ValeurEntree)
{
// Sérialise la valeur d'entrée dans un fichier
}
void EcrireTypeVoiturePrefereDansLeFichier(ETypesDeVoitures TypeVoitureEntree)
{
// Ne compilera pas même si ETypesDeVoitures est un uint8_t car
// l'énumération est déclarée en tant que "classe d'énumération" !
EcrireOctetDansLeFichier(TypeVoitureEntree);
}
///////////////////////////////////////////////////
// Classes et programmation orientée objet
///////////////////////////////////////////////////
#include <iostream>
// Déclare une classe.
// Les classes sont habituellement déclarées dans les fichiers d'en-tête (.h ou .hpp).
class Chien {
// Les variables et fonctions membres sont privées par défaut.
std::string nom;
int poids;
// Tous les membres suivants sont publiques jusqu'à ce que "private:" ou
// "protected:" soit trouvé
public:
// Constructeur par défaut
Chien();
// Déclaractions de fonctions membres (implémentations à suivre)
// Notez que nous utilisons std::string ici au lieu de placer
// using namespace std;
// au-dessus.
// Ne jamais utiliser une instruction "using namespace" dans l'en-tête.
void initialiserNom(const std::string& nomDuChien);
void initialiserPoids(int poidsDuChien);
// Les fonctions qui ne modifient pas l'état de l'objet devraient être
// marquées en constantes avec const.
// Ceci vous permet de les appeler avec une référence constante vers l'objet.
// Notez aussi que les fonctions devant être surchargées dans des classes
// dérivées doivent être explicitement déclarées avec _virtual_.
// Les fonctions ne sont pas virtuelles par défault pour des raisons de
// performances.
virtual void imprimer() const;
// Les fonctions peuvent également être définies à l'intérieur du corps de
// la classe. Ces fonctions sont automatiquement "inline".
void aboyer() const { std::cout << nom << " fait ouaf !\n"; }
// En plus des constructeurs, C++ fournit des destructeurs.
// Ils sont appelés quand l'objet est supprimé ou dépasse le cadre de sa
// portée. Ceci permet de puissants paradigmes tels que RAII
// (voir plus loin)
// Le destructeur devrait être virtuel si la classe est abstraite;
// s'il n'est pas virtuel, alors le destructeur de la classe dérivée ne
// sera pas appelé si l'objet est détruit par le biais d'une référence à la
// classe de base ou d'un pointeur.
virtual ~Chien();
}; // Un point virgule doit clôre la définition de la classe.
// Les fonctions membres de la classe sont habituellement implémentées dans des
// fichiers .cpp.
Chien::Chien()
{
std::cout << "Un chien a été construit\n";
}
// Les objets (comme les chaînes de caractères) devraient être passés par
// référence si vous les modifiez ou par référence constante si vous ne les
// modifiez pas.
void Chien::initialiserNom(const std::string& nomDuChien)
{
nom = nomDuChien;
}
void Chien::initialiserPoids(int poidsDuChien)
{
poids = poidsDuChien;
}
// Notez que le mot-clé "virtual" est nécessaire uniquement à la déclaration,
// et non à la définition.
void Chien::imprimer() const
{
std::cout << "Le chien s'appelle " << nom << " et pèse " << poids << "kg\n";
}
Chien::~Chien()
{
cout << "Au revoir " << nom << " !\n";
}
int main() {
Chien monChien; // imprime "Un chien a été construit"
monChien.initialiserNom("Barkley");
monChien.initialiserPoids(10);
monChien.imprime(); // imprime "Le chien s'appelle Barkley et pèse 10 kg"
return 0;
} // prints "Au revoir Barkley !"
// Héritage :
// Cette classe hérite de toutes les propriétés publiques et protégées de la
// classe Chien ainsi que celles privées, mais n'ont pas accès direct aux
// membres et méthodes privés sans l'aide d'une méthode publique ou protégée
class ChienDomestique : public ChienDomestique {
void definirProprietaire(const std::string& proprietaireDuChien);
// Surcharge le comportement de la fonction d'impression pour tous les
// ChienDomestiques.
// Voir https://fr.wikipedia.org/wiki/Polymorphisme_(informatique)#Polymorphisme_par_sous-typage
// pour une introduction plus générale si vous n'êtes pas familier avec le
// concept de polymorphisme par sous-typage (appelé aussi polymorphisme
// d'inclusion).
// Le mot-clé "override" est optionnel mais assure que vous surchargez bien
// la méthode de la classe de base.
void imprimer() const override;
private:
std::string proprietaire;
};
// Pendant ce temps, dans le fichier .cpp correspondant :
void ChienDomestique::definirProprietaire(const std::string& proprietaireDuChien)
{
proprietaire = proprietaireDuChien;
}
void ChienDomestique::imprimer() const
{
// Appelle la fonction "imprimer" dans la classe de base Chien
Chien::imprimer();
std::cout << "Le chien appartient à " << proprietaire << "\n";
// Affiche "Le chien est <nom> et pèse <poids>"
// "Le chien appartient à <proprietaire>"
}
////////////////////////////////////////////////////
// Initialisation et opérateur de surcharge
////////////////////////////////////////////////////
// En C++, vous pouvez surcharger le comportement d'opérateurs tels
// que +, -, *, /, etc.
// La surcharge se fait en définissant une fonction qui sera appelée à chaque
// fois que l'opérateur sera utilisé.
#include <iostream>
using namespace std;
class Point {
public:
// Les variables membres peuvent avoir des valeurs par défaut
double x = 0;
double y = 0;
// Définit un constructeur par défaut qui ne fait rien
// mais initialise le Point à la valeur par défaut (0, 0)
Point() { };
// La syntaxe suivante s'appelle une liste d'initialisation et est
// la façon correcte d'initialiser les valeurs des membres d'une classe.
Point (double a, double b) :
x(a),
y(b)
{ /* Ne fait rien à part initialiser les valeurs */ }
// Surcharge l'opérateur +
Point operator+(const Point& rhs) const;
// Surcharge l'opérateur +=
Point& operator+=(const Point& rhs);
// Il serait également logique d'ajouter les opérateurs - et -=,
// mais nous les éclipsons par soucis de concision.
};
Point Point::operator+(const Point& rhs) const
{
// Créé un nouveau point qui est la somme de celui-ci de rhs.
return Point(x + rhs.x, y + rhs.y);
}
Point& Point::operator+=(const Point& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
int main () {
Point haut (0,1);
Point droite (1,0);
// Appelle l'opérateur + du Point
// Le point "haut" appelle la fonction + avec "droite" comme paramètre
Point resultat = haut + droite;
// Affiche "Le résultat est haut-droite (1,1)"
cout << "Le résultat est haut-droite (" << resultat.x << ','
<< resultat.y << ")\n";
return 0;
}
////////////////////////////////
// Patrons (templates)
////////////////////////////////
// Les templates (patrons) en C++ sont majoritairement
// utilisés pour la programmation générique, bien qu'ils soient bien plus
// puissants que les constructeurs génériques dans d'autres langages.
// Ils gèrent également la spécialisation explicite et partielle ainsi que
// les classes fonctionnelles; en fait, ils sont un langage fonctionnelles
// Turing-complete embedded in C++ !
// Nous commencons avec le genre de programmation générique auquel vous êtes
// peut-être familier. Pour définir une classe ou fonction qui prend un type de
// paramètre particulier :
template<class T>
class Boite {
public:
// Dans cette classe, T représente n'importe quel type possible.
void inserer(const T&) { ... }
};
// Pendant la compilation, le compilateur génère des copies de chaque template
// avec les paramètres substitués; ainsi, la définition complète de chaque
// classe doit être présente à chaque appel. C'est pourquoi vous verrez les
// classes de templates définies entièrement dans les fichiers d'en-tête.
// Pour instancier une classe de template sur la pile ("stack") :
Boite<int> boiteDEntiers;
// et vous pouvez l'utiliser comme prévu :
boiteDEntiers.inserer(123);
// Vous pouvez, bien sûr, imbriquer les templates :
Boite<Boite<int> > boiteDeBoites;
boiteDeBoites.inserer(boiteDEntiers);
// Jusqu'à C++11, il était nécessaire de placer un espace entre les deux '>'s,
// sinon '>>' était parsé en tant qu'opérateur de décalage vers la droite.
// Vous croiserez peut-être cette syntaxe
// template<typename T>
// à la place. Les mot-clé 'class' et 'typename' sont _généralement_
// interchangeables. Pour plus d'explications, allez à
// http://en.wikipedia.org/wiki/Typename
// ou
// https://fr.wikibooks.org/wiki/Programmation_C-C%2B%2B/Les_templates/Mot-cl%C3%A9_typename
// (oui, ce mot-clé a sa propre page Wikipedia).
// De manière similaire, un patron de fonction :
template<class T>
void aboyerTroisFois(const T& entree)
{
entree.aboyer();
entree.aboyer();
entree.aboyer();
}
// Remarquez ici que rien n'est spécifié à propos du type du paramètre. Le
// compilateur va générer et vérifier le type à chaque appel du patron, c'est
// pourquoi l'appel de fonction suivant marche pour n'importe quel type 'T' qui
// a une méthode constante 'aboyer' !
Chien docile;
docile.initialiserNom("Docile")
aboyerTroisFois(docile); // Affiche "Docile fait ouaf !" trois fois.
// Les paramètres génériques (ou paramètres template) ne sont pas forcément des
// classes :
template<int Y>
void imprimerMessage() {
cout << "Apprenez le C++ en " << Y << " minutes !" << endl;
}
// Vous pouvez explicitement spécialiser les templates pour un code plus
// optimisé. Bien sûr, les utilisations effectives de la spécialisation ne sont
// pas aussi triviales que celle-ci.
// Notez que vous avez toujours besoin de déclarer la fonction (ou classe)
// comme template, même si vous spécifiez explicitement tous les paramètres.
template<>
void imprimerMessage<10>() {
cout << "Apprenez le C++ plus vite en seulement 10 minutes !" << endl;
}
// Affiche "Apprenez le C++ en 20 minutes !"
imprimerMessage<20>();
// Affiche "Apprenez le C++ plus vite en seulement 10 minutes !"
imprimerMessage<10>();
//////////////////////////////////
// Gestion des exceptions
//////////////////////////////////
// La bibliothèque standard fournit quelques types d'exception
// (voir http://en.cppreference.com/w/cpp/error/exception)
// mais n'importe quel type peut être lancé en tant qu'exception.
#include <exception>
#include <stdexcept>
// Toutes les exceptions lancées à l'intérieur d'un block _try_ peuvent être
// attrapées par les blocs de traitement d'erreurs (_catch_ handlers).
try {
// N'allouez pas des exceptions sur le tas (heap) en utilisant _new_.
throw std::runtime_error("Un problème s'est produit");
}
// Attrapez les exceptions avec des références constantes si ce sont des objets
catch (const std::exception& ex)
{
std::cout << ex.what();
}
// Attrape n'importe quelle exception non attrapée par les blocs _catch_
// précédents
catch (...)
{
std::cout << "Exception inconnue attrapée";
throw; // Re-lance l'exception
}
////////////////
// RAII
////////////////
// RAII signifie "Resource Acquisition Is Initialization", soit l'Acquisition
// d'une Ressource est une Initialisation en français.
// Il est souvent considéré comme le paradigme le plus puissant en C++ et
// est le concept simple qu'un constructeur d'un objet acquiert les ressources
// d'un objet et que le destructeur les libère.
// Afin de comprendre son utilité, considérons une fonction qui utilise la
// gestion d'un fichier C :
void faireQuelqueChoseAvecUnFichier(const char* nomDuFichier)
{
// Pour commencer, supposns que rien ne peut échouer.
FILE* fh = fopen(nomDuFichier, "r"); // Ouvre le fichier en lecture
faireQuelqueChoseAvecLeFichier(fh);
faireAutreChoseAvec(fh);
fclose(fh); // Ferme la gestion du fichier.
}
// Malheureusement, les choses deviennent compliquées avec la gestion
// d'erreurs. Supposons que fopen échoue, et que faireQuelqueChoseAvecLeFichier
// et faireAutreChoseAvec retournent des codes d'erreur si elles échouent.
// (Les exceptions sont le meilleur moyen de gérer l'échec, mais des
// programmeurs, surtout avec un passif en C,
// sont en désaccord avec l'utilité des exceptions).
// Nous devons maintenant vérifier chaque appel en cas d'échec et fermer la
// gestion du fichier si un problème se produit.
bool faireQuelqueChoseAvecUnFichier(const char* nomDuFichier)
{
FILE* fh = fopen(nomDuFichier, "r"); // Ouvre le fichier en mode lecture.
if (fh == nullptr) // Le pointeur retourné est null à un échec.
return false; // Signale cet échec à l'appelant.
// Suppose que chaque fonction retourne faux si elle échoue
if (!faireQuelqueChoseAvecLeFichier(fh)) {
fclose(fh); // Ferme le flux d'entrée du fichier pour empêcher les fuites
return false; // Propage l'erreur
}
if (!faireAutreChoseAvec(fh)) {
fclose(fh);
return false;
}
fclose(fh);
return true;
}
// Les programmeurs en C clarifient souvent tout cela en utilisant goto :
bool faireQuelqueChoseAvecUnFichier(const char* nomDuFichier)
{
FILE* fh = fopen(nomDuFichier, "r");
if (fh == nullptr)
return false;
if (!faireQuelqueChoseAvecLeFichier(fh))
goto echec;
if (!faireAutreChoseAvec(fh))
goto echec;
fclose(fh); // Ferme la gestion du fichier
return true; // Indique le succès
echec:
fclose(fh);
return false; // Propage l'erreur
}
// Si les fonctions indiquent des erreurs en utilisant des exceptions,
// les choses sont un peu plus claires, mais toujours sous-optimales.
void faireQuelqueChoseAvecUnFichier(const char* nomDuFichier)
{
FILE* fh = fopen(nomDuFichier, "r"); // Ouvre le fichier en lecture
if (fh == nullptr)
throw std::runtime_error("Ouverture du fichier impossible.");
try {
faireQuelqueChoseAvecLeFichier(fh);
faireAutreChoseAvec(fh);
}
catch (...) {
// Assurez-vous de bien fermer le fichier si une erreur arrive
fclose(fh);
throw; // Puis re-lancer l'exception
}
fclose(fh); // Ferme le fichier
// Tout s'est déroulé correctement
}
// Comparez ceci à l'utilisation de la classe de flux de fichier
// en C++ (fstream).
// fstream utilise son destructeur pour fermer le fichier.
// Pour rappel, les destructeurs sont automatiquement appelée dès qu'un objet
// sort du cadre de sa portée.
void faireQuelqueChoseAvecUnFichier(const std::string& nomDuFichier)
{
// ifstream is short for input file stream
std::ifstream fh(nomDuFichier); // Ouvre le fichier
// Faire des choses avec le fichier
faireQuelqueChoseAvecLeFichier(fh);
faireAutreChoseAvec(fh);
} // Le fichier est automatiquement fermé ici par le destructeur
// Ceci a des avantages _énormes_ :
// 1. Peu importe la situation, la ressource (dans ce cas précis la gestion
// de fichier) sera libérée. Si le destructeur est écrit correctement,
// il est _impossible_ d'oublier de fermer la gestion et d'entraîner une
// une fuite de ressources (si l'objet est sur la pile).
// 2. Remarquez que le code est beaucoup plus clair.
// Le destructeur gère la fermeture du fichier discrètement sans avoir
// besoin de s'en préoccuper.
// 3. Le code est fiable par rapport aux exceptions.
// Une exception peut être lancée n'importe où dans la fonction, le
// nettoyage se fera toujours.
// Tout code C++ idiomatique utilise considérablement RAII pour toutes les
// ressources.
// Des exemples additionnels inclus :
// - La mémoire utilisant unique_ptr et shared_ptr
// - Des conteneurs (containers) - la liste chaînée de la librairie standard,
// des vecteurs (c.-à-d. tableaux auto-redimensionnés), tables de hachage, et
// ainsi de suite. Tous détruisent leur contenu quand ils sortent du cadre
// de leur portée.
// - Les mutex utilisant lock_guard et unique_lock
//////////////////
// Divers
//////////////////
// Ici sont regroupés des aspects du C++ qui peuvent être surprenants aux
// novices (et même à quelques habitués).
// Cette section est, malheureusement, grandement incomplète; C++ est un des
// langages où il est très facile de se tirer soi-même dans le pied.
// Vous pouvez surcharger des méthodes privées !
class Foo {
virtual void bar();
};
class FooSub : public Foo {
virtual void bar(); // Surcharge Foo::bar!
};
// 0 == false == NULL (la plupart du temps) !
bool* pt = new bool;
*pt = 0; // Affecte false à la valeur de la variable pointée par 'pt'.
pt = 0; // Affecte le pointeur null à 'pt'.
// Les deux lignes compilent sans avertissement.
// nullptr est supposé régler un peu ce problème :
int* pt2 = new int;
*pt2 = nullptr; // Ne compile pas
pt2 = nullptr; // Affecte null à pt2
// Il y a une exception faite pour les booléens.
// Ceci vous permet de tester les pointeurs null avec if(!ptr),
// mais par conséquent, vous pouvez assigner nullptr à un booléen directement !
*pt = nullptr; // Ceci compile toujours, même si '*pt' est un booléen !
// '=' != '=' != '='!
// Appelle Foo::Foo(const Foo&) ou une variante du (voir sémantiques de mouvement)
// constructeur par copie.
Foo f2;
Foo f1 = f2;
// Appelle Foo::Foo(const Foo&) ou une variante, mais copie seulement la partie
// 'Foo' de 'fooSub'. Tout membre extra de 'fooSub' est ignoré.
// Ce comportement parfois horrifiant est appelé "object slicing".
FooSub fooSub;
Foo f1 = fooSub;
// Appelle Foo::operator=(Foo&) ou une variante.
Foo f1;
f1 = f2;
// Comment vraiment nettoyer un conteneur :
class Foo { ... };
vector<Foo> v;
for (int i = 0; i < 10; ++i)
v.push_back(Foo());
// La ligne suivante affecte la taille de v à 0, mais les destructeurs ne sont
// appelés et les ressources ne sont pas libérées !
v.empty();
// La nouvelle valeur est copiée dans le premier Foo que nous avons inséré
v.push_back(Foo());
// Ceci nettoie toutes les valeurs de v. Voir la section à propos des objets
// temporaires pour comprendre pourquoi cela fonctionne.
v.swap(vector<Foo>());
```
Lecture complémentaire :
Une référence à jour du langage est disponible à
<http://cppreference.com/w/cpp>
Des ressources supplémentaires sont disponibles à <http://cplusplus.com>
|