I. GCC 4.8▲
La version 4.8 est encore en cours de développement et reste expérimentale, elle est disponible pour effectuer des tests uniquement. L'un des plus gros changements de gcc 4.8 est interne : gcc a été complètement réécrit en C++ au lieu du C utilisé avant. Pour l'installer sur Ubuntu, vous pouvez installer le paquet gcc-snapshot. Pour l'installer sur Windows, vous pouvez télécharger l'installeur sur cette page. Le détail des nouveautés de gcc 4.8 sont disponibles dans la documentation de gcc (voir les parties C++ et Runtime Library (libstdc++)) et il existe un guide pour le portage vers gcc 4.8.
Au niveau langage, cette version supporte maintenant le mot clé thread_local, les attribues, le mot clé, les constructeurs délégués et l'option de compilation -std=c++1y pour le support du prochain standard du C++ (il est prévu un C++14 et un C++17 normalement).
II. GCC 4.7▲
II-A. Options de compilation▲
Gcc accepte les nouvelles options suivantes, permettant d'activer le support du C++11 : -std=c++11, -std=gnu++11 et -Wc++11-compat. L'option -Wdelete-non-virtual-dtor permet d'activer les alertes lorsque l'on définit une classe possédant une fonction virtuelle, mais ayant un destructeur non virtuel. Cette option est également activée avec -Wall. Voir la FAQ Pourquoi et quand faut-il créer un destructeur virtuel ?
L'option -Wzero-as-null-pointer-constant permet d'activer les alertes lorsque l'on utilise la constante 0 comme pointeur NULL à la place de nullptr.
II-B. Initialisation des données membres non statiques▲
Norme C++ : N2756.
Seules les variables membres statiques et constantes pouvaient être initialisées directement dans la déclaration de la classe. Les autres variables membres devaient être initialisées dans les constructeurs.
class
A {
static
const
int
m1 =
7
; // ok
const
int
m2 =
7
; // erreur: non statique
static
int
m3 =
7
; // erreur: non constant
static
const
int
m4 =
var; // erreur: n'est pas une expression constante
static
const
string m5 =
"odd"
; // erreur: n'est pas un type intégral
}
;
Il est maintenant possible d'initialiser des variables membres non statiques directement lors de la déclaration.
struct
A {
int
i =
42
;
}
a;
// est équivalent à :
struct
A {
int
i;
A() : i(42
) {}
}
a;
Cette fonctionnalité est particulièrement intéressante lorsque l'on a plusieurs constructeurs :
class
A {
public
:
A(): a(7
), b(5
), hash_algorithm("MD5"
), s("Constructor run"
) {}
A(int
a_val) : a(a_val), b(5
), hash_algorithm("MD5"
), s("Constructor run"
) {}
A(D d) : a(7
), b(g(d)), hash_algorithm("MD5"
), s("Constructor run"
) {}
int
a, b;
private
:
HashingFunction hash_algorithm;
std::
string s;
}
;
// devient :
class
A {
public
:
A() {}
A(int
a_val) : a(a_val) {}
A(D d) : b(g(d)) {}
int
a =
7
;
int
b =
5
;
private
:
HashingFunction hash_algorithm{
"MD5"
}
;
std::
string s{
"Constructor run"
}
;
}
;
Il est également possible d'initialiser une variable membre à partir d'une autre variable membre ou d'une fonction membre (statique ou non) :
class
Widget {
private
:
int
x {
5
}
;
int
y {
2
*
x }
;
std::
string id =
{
defaultID() }
;
}
;
II-C. Alias de templates▲
Norme C++ : N2258.
Il n'est pas possible de déclarer un typedef pour déclarer un type template. Pour contourner cela, il faut déclarer une nouvelle classe template :
template
<
class
T>
typedef
T*
Ptr; // illégal
template
<
class
T>
Ptr {
typedef
T*
type; }
; // légale
Ptr<
int
>
::
type ip; // decltype(ip) = int*
Le C++11 ajoute cette fonctionnalité, non pas en utilisant un typedef (pour des raisons syntaxiques), mais en utilisant le mot clé using :
template
<
class
T>
using
Ptr =
T*
;
Ptr<
int
>
ip; // decltype(ip) = int*
On pourra de cette manière faire des spécialisations partielles de template :
template
<
typename
T>
using
MyAllocVec =
std::
vector<
T, MyAllocator>
;
MyAllocVec<
int
>
v; // std::vector<int, MyAllocator>
template
<
std::
size_t N>
using
StringArray =
std::
array<
std::
string, N>
;
StringArray<
15
>
sa; // std::array<std::string, 15>
Les alias de templates ne peuvent pas être spécialisés :
template
<
typename
T>
using
MyAllocVec =
std::
vector<
T, MyAllocator>
;
template
<
typename
T>
using
MyAllocVec =
std::
vector<
T*
, MyPtrAllocator>
; // erreur
Il faut alors passer par une classe de trait :
template
<
typename
T>
// trait de base
struct
VecAllocator {
typedef
MyAllocator type; }
;
template
<
typename
T>
// trait spécialisé
struct
VecAllocator<
T*>
{
typedef
MyPtrAllocator type; }
;
template
<
typename
T>
using
MyAllocVec =
std::
vector<
T, typename
VecAllocator<
T>
::
type>
;
Lorsque l'on ne déclare pas une classe template, la syntaxe avec using peut être utilisée à la place de celle avec typedef pour améliorer la lisibilité :
typedef
void
(*
CallBackPtr)(int
);
using
CallBackPtr =
void
(*
)(int
); // plus lisible
II-D. Constructeurs délégués▲
Norme C++ : N1986.
Avant, lorsque l'on souhaitait déclarer plusieurs constructeurs, on retrouvait le même code recopié dans plusieurs constructeurs, ce qui alourdissait inutilement le code et nuisait à la maintenance. Avec le C++11, un constructeur peut appeler un autre constructeur directement. On peut alors mettre dans un constructeur que le code qui lui est spécifique.
struct
A {
A(int
);
A(): A(42
) {
}
// délègue au constructeur A(int)
}
;
Les constructeurs délégués sont compatibles avec les mots clés inline, explicit, private, protected et public.
II-E. Littérales définies par l'utilisateur▲
Norme C++ : N2765.
Le C++ accepte d'ajouter un qualificateur à la suite des constantes que l'on déclare, pour préciser le type de cette constante. Par exemple 1f indique que l'on déclare un float alors que 1L indique que l'on déclare un long. Il est maintenant possible de définir ses propres qualificateurs en utilisant l'opérateur littéral operator"" :
constexpr
long
double
operator
""
_degrees (long
double
d) {
return
d *
0.0175
; }
long
double
pi =
180.0
_degrees;
On peut remarquer l'utilisation (optionnelle) de constexpr pour permettre l'évaluation lors de la compilation. Il faut faire attention à ne pas oublier l'espace entre "" et le suffixe lors de la déclaration de l'opérateur.
On peut par exemple utiliser cette approche pour forcer l'utilisation de std::string au lieu de const char* lors de la déclaration d'une chaîne de caractères :
std::
string operator
""
_s (const
char
*
p, size_t n) {
return
string(p,n); }
template
<
class
T>
void
f(const
T&
);
f("Hello"
); // utilise un const char*
f("Hello"
_s); // utilise un std::string
Les caractères précédant le qualificateur peuvent être récupérés dans un type const char*, sans qu'il soit nécessaire d'encadrer ces caractères entre deux " :
Bignum operator
""
_x(const
char
*
p) {
return
Bignum(p); }
void
f(Bignum);
f(1234567890123456789012345678901234567890
_x); // pas besoin d'écrire "1234567890123456789012345678901234567890"_x
Les littérales définies par l'utilisateur acceptent quatre types de paramètres en entrée :
- les entiers, en utilisant les types unsigned long long ou const char* ;
- les nombres réels, en utilisant les types long double ou const char* ;
- les chaînes de caractères, en utilisant la paire d'arguments (const char*, size_t) ;
- un caractère, en utilisant le type char.
Seuls les suffixes commençant par « _ » sont autorisés. Les autres sont réservés pour un usage ultérieur par la norme.
II-F. Déclarations étendues de l'amitié▲
Norme C++ : N1791.
L'amitié peut maintenant se transmettre via les typdef et les template :
class
C;
typedef
C Ct;
class
X1 {
friend
C; // OK : la classe C est amie
}
;
class
X2 {
friend
Ct; // OK : la classe C est amie
friend
class
D; // OK : on déclare une nouvelle classe qui sera amie
}
;
template
<
typename
T>
class
R {
friend
T;
}
;
R<
C>
rc; // OK : la classe C est amie de la classe R<C>
R<
int
>
Ri; // OK : l'amitié est ignorée pour les types de base
II-G. Surcharges explicites de la virtualité▲
Lorsque l'on dérive une classe, il est relativement facile de se tromper sur la signature d'une fonction. On déclare alors une nouvelle fonction au lieu de surcharger une fonction du parent.
struct
B {
virtual
void
f();
virtual
void
g() const
;
virtual
void
h(char
);
void
k();
}
;
struct
D : B {
void
f(); // dérive de B::f()
void
g(); // ne dérive pas de B::g() (fonction non constante)
virtual
void
h(char
); // dérive de B::h(char)
void
k(); // ne dérive pas de B::k() (B::k() n'est pas virtuelle)
}
;
Le C++11 introduit deux nouveaux mots clés permettant de spécifier qu'une fonction n'est pas dérivable (mot clé final) ou imposer qu'une fonction doive dériver d'une autre fonction (mot clé override).
struct
B {
virtual
void
f() final
;
virtual
void
g() const
;
virtual
void
h(char
);
void
k();
}
;
struct
D : B {
void
f(); // erreur : B::f() ne peut être dérivable
void
g() override
; // erreur : ne dérive pas de B::g()
virtual
void
h(char
); // dérive de B::h(char), mais génère une alerte
void
k() override
; // erreur : B::k() n'est pas virtuel
}
;
Il est également possible de déclarer une classe comme étant final :
struct
E final
{
}
;
struct
F: E {
}
; // erreur : dérive d'une classe finale
III. GCC 4.6▲
III-A. Options de compilation▲
Gcc accepte les nouvelles options suivantes, permettant d'activer le support du C++0x : -std=c++0x, -std=gnu++0x et -Wc++0x-compat.
III-B. La constante nullptr▲
Norme C++ : N2431.
Le pointeur null a longtemps été défini comme étant égal à 0. Cela pouvait poser des problèmes lors de la résolution des fonctions à appeler. En effet, 0 était considéré avant tout comme un int et non comme un pointeur null.
void
f( char
*
);
void
f( int
);
f( 0
); // appel de f(int)
Le mot clé nullptr remplace l'utilisation de la constante 0 pour n'importe quel type de pointeur.
char
*
p =
nullptr
;
int
*
q =
nullptr
;
Ce nouveau mot clé permet de lever l’ambiguïté lors de la résolution de l'appel des fonctions :
void
f(int
);
void
f(char
*
);
f(nullptr
); // appel de f(char*)
nullptr n'est pas assignable à un entier :
int
i =
nullptr
; // erreur
Pour des raisons de compatibilité du code, la constante 0 est encore utilisable, mais il est recommandé de passer progressivement à nullptr.
char
*
p2 =
0
; // continue de fonctionner
III-C. Déclaration anticipée pour les énumérations▲
Norme C++ : N2764.
Il est maintenant possible de déclarer par anticipation les énumérations à typage fort (strongly-typed enums), introduites dans gcc 4.4.
enum
class
Color_code : char
; // déclaration
void
foobar(Color_code*
p); // utilisation de la déclaration
enum
class
Color_code : char
{
red, yellow, green, blue }
; // définition
III-D. Les expressions constantes généralisées▲
Norme C++ : N2235.
Les expressions constantes permettent de spécifier avec le mot clé constexpr qu'une expression, une fonction ou un constructeur pourra être évalué lors de la compilation.
enum
Flags {
good=
0
, fail=
1
, bad=
2
, eof=
4
}
;
Flags operator
&
(Flags f1, Flags f2) {
return
Flags( int
(f1) &
int
(f2) ); }
constexpr
Flags operator
|
(Flags f1, Flags f2) {
return
Flags( int
(f1) |
int
(f2) ); }
void
f(Flags x)
{
switch
(x) {
case
bad: break
;
case
fail&
eof: break
; // erreur, ne peut être évalué à la compilation
case
bad|
eof: break
; // ok, expression constante
default
:
break
;
}
}
Les expressions peuvent être utilisées pour réaliser des calculs à la compilation, similaire à ce que l'on fait avec les fonctions templates (voir par exemple La métaprogrammation en C++ - Calcul et optimisation mathématique) :
#include
<iostream>
using
std::
cout;
using
std::
endl;
// version expression constante
constexpr
unsigned
constexpr_pow(unsigned
base, unsigned
exp)
{
return
(exp==
0
) ? 1
: (base *
constexpr_pow(base, exp-
1
));
}
// version template
template
<
unsigned
base, unsigned
exp>
struct
template_pow {
enum
{
value =
base *
template_pow<
base, exp -
1
>
::
value }
;
}
;
template
<
unsigned
base>
struct
template_pow<
base, 0
>
{
enum
{
value =
1
}
;
}
;
int
main() {
cout
<<
"Avec constexpr: "
<<
constexpr_pow(5
, 3
) <<
endl
<<
"Avec template: "
<<
template_pow<
5
, 3
>
::
value <<
endl;
}
III-E. Les unions sans restrictions▲
Norme C++ : N2544.
Avec le C++98, il n'était pas possible d'utiliser des types possédant un constructeur, un destructeur ou un opérateur d'affectation défini par l'utilisateur.
union
U {
int
m1;
complex<
double
>
m2; // erreur
string m3; // erreur
}
;
Le C++11 accepte maintenant d'utiliser des types possédant un constructeur, un destructeur ou un opérateur d'affectation défini par l'utilisateur dans une union. Ces fonctions définies par l'utilisateur sont alors supprimées.
union
U1 {
int
m1;
complex<
double
>
m2; // ok
}
;
union
U2 {
int
m1;
string m3; // ok
}
;
Il est alors nécessaire d'utiliser les unions dans des structures appelant manuellement les fonctions qui ont été supprimées. Voici un exemple de code utilisant un membre de type string dans une union. Il est alors nécessaire d'appeler explicitement le destructeur de la chaîne et de faire un placement new pour créer une nouvelle chaîne.
class
Widget {
// 3 implémentations alternatives représentées par une union
private
:
enum
class
Tag {
point, number, text }
type; // discriminant
union
{
// représentation
point p; // point possède un constructeur
int
i;
string s; // string possède un constructeur, un destructeur et permet la copie
}
;
widget&
operator
=
(const
widget&
w) // nécessaire à cause de string
{
if
(type==
Tag::
text &&
w.type==
Tag::
text) {
// si les 2 widgets sont de type string
s =
w.s; // on réalise une copie classique des string
return
*
this
;
}
if
(type==
Tag::
text) s.~
string(); // on détruit explicitement la chaîne
switch
(type==
w.type) {
case
Tag::
point: p =
w.p; break
; // copie normale
case
Tag::
number: i =
w.i; break
; // copie normale
case
Tag::
text: new
(&
s)(w.s); break
; // placement new
}
type =
w.type;
return
*
this
;
}
}
;
III-F. Range-based for▲
Norme C++ : N2930.
Le range-based for permet de parcourir la totalité d'un conteneur. Cette syntaxe est compatible avec tous les conteneurs standards et les conteneurs définis par l'utilisateur en fournissant soit les fonctions membres x.begin() et x.end(), soit en fournissant les fonctions libres begin(x) et end(x). Cette syntaxe est compatible en particulier avec std::string ou les listes d'initialisation.
vector<
double
>
v;
for
(double
d : v) {
cout <<
d <<
endl; }
// syntaxe de base
for
(auto
d : v) {
cout <<
d <<
endl; }
// utilisation du mot clé auto
for
(auto
&
d : v) {
++
d; }
// référence pour modifier les éléments
for
(auto
const
&
d : v) {
+
+
d; }
// passage par référence constante
// liste d'initialisation
for
(auto
x : {
1
, 2
, 3
, 5
, 8
, 13
, 21
, 34
}
) {
cout <<
x <<
endl; }
III-G. Règles sur la génération automatique pour le déplacement et la copie▲
Norme C++ : N3053.
Cette version de gcc implémente les règles proposées dans le document N3053 relatives à la génération automatique des opérations de copie et de déplacement. Ces différentes règles permettent de comprendre quand et pourquoi le compilateur ajoute les constructeurs et opérateurs de copie et de déplacement :
- Les types fondamentaux du C++ (build-in types) proposent la sémantique de déplacement similaire à la copie ;
- Une structure est correctement formée pour la copie si et seulement si ses bases et ses membres sont correctement formés pour la copie ;
- Une structure est correctement formée pour le déplacement si et seulement si ses bases et ses membres sont correctement formés pour le déplacement ;
- Les fonctions pour la copie et le déplacement peuvent être spécifiés avec =delete et =default ;
-
La sémantique de déplacement est implicitement déclarée si la structure est correctement formée et si la copie n'est pas déclarée par l'utilisateur (en utilisant =default par exemple). On doit alors ajouter explicitement le constructeur par déplacement avec : default.
Sélectionnezstruct
no_move{
no_move(const
no_move&
)=
default
;}
;struct
has_move{
has_move(const
has_move&
)=
default
; has_move( has_move&&
)=
default
;}
; -
De manière identique, la copie est supprimée si le déplacement est déclaré par l'utilisateur.
Sélectionnezstruct
no_copy{
no_copy( no_copy&&
)=
default
;}
;struct
has_copy{
has_copy(const
has_copy&
)=
default
; has_copy( has_copy&&
)=
default
;}
; - Les unions suppriment implicitement le déplacement si l'un de leurs membres possède un déplacement non trivial. Idem pour la copie.
struct
non_trival_copier {
non_trival_copier( const
non_trival_copier&
) =
delete
;
}
;
struct
non_trivial_mover {
non_trival_copier( non_trival_copier&&
) =
delete
;
}
;
union
wrap_copy {
non_trival_copier c;
// implicit wrap_copy::wrap_copy( const wrap_copy& ) = delete;
}
;
union
wrap_move {
non_trivial_mover m;
// implicit wrap_move::wrap_move( wrap_move&& ) = delete;
}
;
union
wrap_both {
non_trival_copier c;
non_trivial_mover m;
// implicit wrap_both::wrap_both( const wrap_both& ) = delete;
// implicit wrap_both::wrap_both( wrap_both&& ) = delete;
}
;
III-H. Autoriser les constructeurs par déplacement à lancer une exception▲
Norme C++ : N3050.
Dans le document N2855, Doug Gregor et Dave Abrahams ont présenté la problématique qui pouvait se poser dans certains cas avec les templates, les constructeurs par déplacement et les fonctions de la STL. Le choix a été fait alors d'interdire purement et simplement les constructeurs par déplacement à lancer des exceptions. Ce choix pose d'autres problèmes, en particulier pour assurer les garanties des classes et la compatibilité. D'autant plus que les problématiques potentielles ne concernent que quelques cas particuliers. La nouvelle fonction std::move_if_noexcept(x) introduite dans cette version de gcc permet de corriger ce problème et d'autoriser l'utilisation des exceptions dans les constructeurs par déplacement. Au lieu d'utiliser std::move(x), qui autorise à utiliser n'importe quel constructeur par déplacement, std::move_if_noexcept(x) autorise à utiliser uniquement les constructeurs par déplacement qui ne génèrent pas d'exception. Dans le cas contraire, le compilateur utilisera le constructeur par copie.
struct
A {
A() =
default
; // constructeur par défaut
A(const
A&
) =
default
; // constructeur par copie
A(A&&
) =
default
; // constructeur par déplacement
}
;
struct
B {
B() =
default
; // constructeur par défaut
B(const
B&
) =
default
; // constructeur par copie
B(B&&
) noexcept
=
default
; // constructeur par déplacement noexcept
}
;
struct
C {
C() =
default
; // constructeur par défaut
C(const
C&
) =
delete
; // pas de constructeur par copie
C(C&&
) =
default
; // pas de constructeur par déplacement noexcept
}
;
std::
move_if_noexcept(a); // ok, appel du constructeur par copie
std::
move_if_noexcept(b); // ok
std::
move_if_noexcept(c); // erreur
En complément, cette version de gcc introduit le mot clé noexcept qui permet de spécifier qu'une fonction ne lance pas d'exception et ne récupère pas les exceptions qui seraient lancées par les fonctions appelées. Deux syntaxes sont possibles : noexcept et noexcept (expression).
void
foo() noexcept
{}
// foo ne lance pas d'exception
template
<
class
T>
foo2(foo<
T>&&
x) noexcept
(is_nothrow_constructible<
T,T&&>
::
value)
// foo2 ne lance pas d'exception que si T n'en lance pas non plus
{}
IV. GCC 4.5▲
IV-A. Les fonctions lambdas▲
Norme C++ : N2927.
Les fonctions lambda sont l'ajout le plus important de gcc 4.5. En C++98, il faut en général créer un foncteur (un objet-fonction) ou une fonction libre pour utiliser les algorithmes de la bibliothèque standard :
vector<
int
>
v =
{
50
, -
10
, 20
, -
30
}
;
// tri par défaut
std::
sort(v.begin(), v.end());
// maintenant, v contient { -30, -10, 20, 50 }
// tri selon la valeur absolue
// avec une fonction libre
bool
myfunction(int
a, int
b) {
return
abs(a)<
abs(b); }
std::
sort(v.begin(), v.end(), myfunction);
// avec un foncteur
struct
myclass {
bool
operator
() (int
a, int
b) {
return
abs(a)<
abs(b); }
}
myobject;
std::
sort(myvector.begin(), myvector.end(), myobject);
Les lambdas (ou fonctions lambda ou expressions lambda) permettent de créer facilement des foncteurs directement dans l'appel de l'algorithme :
// tri selon la valeur absolue
std::
sort(v.begin(), v.end(), [](int
a, int
b) {
return
abs(a)<
abs(b); }
);
// maintenant, v contient { -10, 20, -30, 50 }
La syntaxe générale des lambdas est la suivante :
[ liste de capture ] ( paramètres ) retour {
code }
avec :
- Liste de capture : liste de variables déclarées hors de la lambda et qui seront accessibles dans la lambda ;
- Paramètres (optionnel) : paramètres qui seront envoyés par l'algorithme ;
- Retour (optionnel) : type retourné par la lambda ;
- Code : corps de la fonction lambda.
Les paramètres passés à la lambda
L'algorithme std::sort a besoin d'un prédicat qui prend deux paramètres en entrée et renvoie un booléan.
Les paramètres capturés dans la lambda
Il est possible d'accéder à des variables déclarées en dehors de la lambda :
vector<
int
>
indices(v.size());
int
count =
0
;
generate(indices.begin(),indices.end(),[&
count](){
return
++
count; }
);
- [] : capture aucune variable ;
- [&] : capture toutes les variables par référence ;
- [=] : capture toutes les variables par copie ;
- [&count] : capture la variable count par référence ;
- [=count] : capture la variable count par copie.
Les types de retour
Habituellement, le type de retour est automatiquement déterminé à partir de l'expression return. Si aucune valeur n'est retournée, le type de retour est void. Mais il est également possible de préciser explicitement le type de retour avec la syntaxe de type de retour en suffixe.
// type de retour fixe
generate(indices.begin(),indices.end(),[&
count]() ->
int
{
return
++
count; }
);
// type de retour déterminé avec decltype
generate(indices.begin(),indices.end(),[&
count]() ->
decltype
(++
count) {
return
++
count; }
);
IV-B. Les opérateurs de conversion explicites▲
Norme C++ : N2437.
En C++, les constructeurs prenant un seul paramètre en entrée sont appelés constructeurs de conversion (excepté celui prenant le même type et qui est appelé constructeur par copie). Ces constructeurs peuvent être appelés si nécessaire par le compilateur pour convertir les types.
struct
S {
S(int
); }
; // constructeur de conversion de int vers S
S s1(1
); // ok, appel direct du constructeur
S s2 =
1
; // ok, conversion implicite
void
f(S);
f(1
); // ok, conversion implicite
Cependant, il est parfois non souhaitable de laisser le compilateur convertir implicitement des types. On peut alors ajouter le mot clé explicit devant le constructeur pour spécifier au compilateur que ce constructeur ne peut pas être implicitement appelé.
struct
E {
explicit
E(int
); }
; // constructeur explicite
E e1(1
); // ok, appel direct du constructeur
E e2 =
1
; // erreur, pas de conversion implicite
void
f(E);
f(1
); // erreur, pas de conversion implicite
Pour convertir des types entre eux, il est également possible de définir des opérateurs de conversion. Les opérateurs de conversion ne prennent pas de paramètre en entrée et en sortie et possèdent leurs noms correspondant aux types de retour. Ils peuvent également être appelés implicitement par le compilateur. Lorsqu'un type ne propose pas de conversion implicite d'un type vers un autre, il est ainsi possible d'ajouter cette conversion en passant par une classe intermédiaire.
struct
S {
S(int
) {
}
}
; // constructeur de conversion de int vers S
struct
SS {
int
m;
SS(int
x) : m(x) {
}
// constructeur de conversion de int vers SS
operator
S() {
return
S(m); }
// opérateur de conversion de SS vers S
}
;
SS ss(1
); // ok, appel direct du constructeur
S s1 =
ss; // ok, opérateur de conversion, syntaxe similaire à un constructeur de conversion implicite
S s2(ss); // ok, opérateur de conversion
void
f(S);
f(ss); // ok, opérateur de conversion
Dans certains cas, il n'est pas non plus souhaitable que cette conversion soit implicite. Avec le C++98, il n'existe pas de mécanisme permettant de spécifier que l'opérateur de conversion était explicite. Le C++11 ajoute donc cette fonctionnalité en permettant d'ajouter le mot clé explicit devant l'opérateur.
struct
S {
S(int
) {
}
}
; // constructeur de conversion de int vers S
struct
SS {
int
m;
SS(int
x) : m(x) {
}
// constructeur de conversion de int vers SS
explicit
operator
S() {
return
S(m); }
// opérateur de conversion explicite de SS vers S
}
;
SS ss(1
); // ok, appel direct du constructeur
S s1 =
ss; // erreur, pas de conversion implicite
S s2(ss); // ok, conversion explicite
void
f(S);
f(ss); // error, pas de conversion implicite
IV-C. Les chaînes littérales brutes et unicode▲
Norme C++ : N2442.
En C++, les caractères spéciaux sont codés dans les chaînes de caractères à l'aide du caractère d'échappement '\'. Par exemple, '\n' représente un retour à la ligne et '\t' représente une tabulation. Pour entrer le caractère '\', il faut donc le doubler pour ne pas le confondre avec le caractère d'échappement '\\'. Dans certains cas, par exemple dans les expressions régulières, on utilise souvent le caractère '\'. Il devient alors fastidieux de doubler ce caractère à chaque fois. Par exemple, pour écrire l'expression régulière correspondant à deux mots séparés par '\' s'écrit « \w\\\w ». Elle sera codée en C++ par :
string s =
"
\\
w
\\\\\\
w"
;
Le risque d'erreur est alors important. Le C++11 ajoute la possibilité d'exprimer directement les chaînes brutes :
string s =
R"(\w\\\w
)";
Pour les cas particuliers (par exemple pour ajouter la chaîne ") dans une chaîne brute) il est possibilité d'ajouter des caractères aux délimiteurs "( et )" pour éviter qu'il puisse y avoir confusion. Par exemple, on peut ajouter *** comme délimiteurs supplémentaires. La chaîne brute s'écrit alors :
string s =
R"***( ...
)***";
string s =
R"(Une chaîne qui contient ") peut poser des problèmes
)";
// erreur, la première occurrence de )" est considérée comme étant le délimiteur de droite
string s =
R"***(Une chaîne qui contient ") peut poser des problèmes
)***";
// ok, )" n'est pas reconnu comme délimiteur
Les chaînes brutes peuvent être précédées par un préfixe spécifiant l'encodage (u8, u, U ou L).
string s =
u8R"(fdfdfa
)"; // chaîne codée en UTF-8
IV-D. Les caractères universels dans les littérales▲
Norme C++ : N2170.
La norme du C++ spécifie les types de caractères qui sont acceptés ou non dans les chaînes. En particulier, les caractères de contrôles (00 à 1F et 7F à 9F) sont interdits. Il est alors nécessaire d'utiliser les caractères d'échappement pour les utiliser dans une chaîne, ce qui peut rendre le code dépendant de la plateforme. Par exemple, la chaîne "\u0085" en windows-1252 sera écrite "\xC2\x85" en UTF-8. Pour améliorer les supports de l'unicode, le C++11 lève cette limitation.
IV-E. Les types POD revisités▲
Norme C++ : N2342.
Les types POD (Plain Old Data) sont des structures en C++ similaires aux structures en C, qui peuvent être en particulier copiées avec memcpy() et initialisées avec memset().
struct
S1 {
int
a; }
; // S1 est un type POD
struct
S2 {
int
a; S2(int
aa) : a(aa) {
}
}
; // S2 n'est pas un type POD
struct
S3 {
virtual
void
f(); /* ... */
}
; // S3 n'est pas un type POD
Le C++11 étend les types POD aux structures simples qui peuvent être copiées avec memcpy(). Le type S2 devient donc un type POD dans la nouvelle norme. Pour qu'un type soit un POD, il faut donc que :
- tous ses membres soient des types POD ;
- aucune fonction virtuelle ;
- pas de base virtuelle ;
- pas de référence ;
- pas d'accès multiple.
IV-F. Les types locaux et non nommés comme arguments template▲
Norme C++ : N2657.
En C++98, les types locaux et non nommés ne pouvaient pas être utilisés comme arguments template. Il fallait alors déclarer les foncteurs à distance du code les utilisant, ce qui diminuait la lisibilité des algorithmes de la STL. Le C++11 permet de lever cette interdiction.
void
f(vector<
X>&
v)
{
struct
Less {
bool
operator
()(const
X&
a, const
X&
b) {
return
a.v<
b.v; }
}
;
sort(v.begin(), v.end(), Less());
// en C++98 : erreur, Less est local ; en C++11 : ok
}
Le C++11 autorise également l'utilisation des types non nommés comme arguments template.
template
<
typename
T>
void
foo(T const
&
t){}
enum
X {
x }
;
enum
{
y }
;
int
main()
{
foo(x); // C++98 : ok; C++11 : ok
foo(y); // C++98 : erreur; C++11 : ok
enum
Z {
z }
;
foo(z); // C++98 : error; C++11 : ok
}
V. GCC 4.4▲
V-A. Paramètres template étendus pour les templates template variadic▲
Norme C++ : N2555.
Lors de l'ajout des variadics templates dans gcc 4.3, il n'était pas possible de les utiliser pour les template template. Pour les bibliothèques utilisant des variadic template template, il était donc nécessaire de dupliquer le code, comme cela se faisait lorsque les variadic template n'étaient pas disponibles. Par exemple, ce code utilisant Boost.MPL (metaprogramming language) :
typedef
mpl::
vector<
int
, short
, float
>
v;
typedef
mpl::
transform<
v, add_pointer<
_1>
>
::
type v2;
BOOST_MPL_ASSERT((is_same<
mpl::
at_c<
v2,_0>
::
type, int
*>
));
BOOST_MPL_ASSERT((is_same<
mpl::
at_c<
v2,_1>
::
type, short
*>
));
BOOST_MPL_ASSERT((is_same<
mpl::
at_c<
v2,_2>
::
type, float
*>
))
Pour implémenter ces fonctions, il faut créer des spécialisations de template template :
template
<
typename
T>
struct
eval;
template
<
template
<
class
>
class
T, class
U>
struct
eval<
T<
U>
>
{
/*...*/
}
;
template
<
template
<
class
,class
>
class
T, class
U, class
V>
struct
eval<
T<
U, V>
>
{
/*...*/
}
;
// etc.
Avec les variadic template, on pourrait écrire le code de cette manière :
template
<
template
<
typename
...>
class
T, typename
... U>
struct
eval<
T<
U...>
>
{
/*...*/
}
;
Malheureusement, la norme indique que T ne pourra correspondre qu'avec les templates possédant la même signature, donc uniquement des variadic template. Ici, std::tuple‹int, float> (dont la définition est std::tuple‹typename...>) pourra correspondre, mais std::pair‹int, float> (dont la définition est std::pair‹T, U>) ne le pourra pas. Il est donc nécessaire de continuer à déclarer d'autres formes de template pour correspondre aux autres signatures possibles :
template
<
template
<
typename
...>
class
T, typename
... U>
struct
eval<
T<
U...>
>
{
/*...*/
}
;
template
<
template
<
class
, typename
...>
class
T, class
U, typename
... V>
struct
eval<
T<
U, V...>
>
{
/*...*/
}
;
// etc.
Cette limitation est inutile et peut troubler les utilisateurs. Il est alors nécessaire de faciliter la correspondance des variadic template template avec les syntaxes que l'on pourra attendre. La règle est donc la suivante :
Étant donné un paramètre template template P dont la liste de paramètres template se termine par un « template parameter pack », P peut correspondre avec n'importe quel argument template A avec une liste de paramètres template qui est identique à la liste des paramètres template de P jusqu'au « pack », suivi par une liste de paramètres template qui correspond avec le pack en type et en genre « kind » (mais qui n'est pas nécessairement un pack lui-même).
Le code suivant :
template
<
template
<
typename
...>
class
T, typename
... U>
struct
eval<
T<
U...>
>
{
/*...*/
}
;
peut ainsi correspondre à :
template
<
typename
...>
class
A;
template
<
typename
T>
class
B;
template
<
typename
T1, typename
T2>
class
C;
template
<
typename
T1, typename
T2, typename
...>
class
D;
Voici d'autres exemples de correspondance :
template
<
typename
T>
struct
eval;
template
<
template
<
typename
, typename
...>
class
TT, typename
T1, typename
... Rest>
struct
eval<
TT<
T1, Rest...>
>
{
}
;
template
<
typename
T1>
struct
A;
template
<
typename
T1, typename
T2>
struct
B;
template
<
int
N>
struct
C;
template
<
typename
T1, int
N>
struct
D;
template
<
typename
T1, typename
T2, int
N =
17
>
struct
E;
eval<
A<
int
>>
eA; // ok, correspond à la spécialisation partielle
eval<
B<
int
, float
>>
eB; // ok, correspond à la spécialisation partielle
eval<
C<
17
>>
eC; // erreur, ne correspond pas à la spécialisation partielle
eval<
D<
int
, 17
>>
eD; // erreur, ne correspond pas à la spécialisation partielle
eval<
E<
int
, float
>>
eE; // erreur, ne correspond pas à la spécialisation partielle
V-B. Initializer-list▲
Norme C++ : N2672.
Pour initialiser un tableau statique en C++98, on peut fournir une liste d'éléments pour l'initialisation (array-initializer). Si la taille n'est pas spécifiée, celle-ci est déterminée en fonction du nombre d'éléments de la liste. Si la taille est spécifiée, il faut que cette taille soit supérieure ou égale au nombre d'éléments de la liste. Le reste du tableau est complété avec les valeurs par défaut si nécessaire.
// initialisation
int
values[] =
{
1
, 2
, 3
, 4
, 5
}
; // ok
int
values[10
] =
{
1
, 2
, 3
, 4
, 5
}
; // ok, contient { 1, 2, 3, 4, 5, 0, 0, 0, 0, 0 }
int
values[2
] =
{
1
, 2
, 3
, 4
, 5
}
; // erreur
Si on souhaitait créer un tableau dynamique en utilisant un vector, il était nécessaire de passer par le tableau statique avant :
vector<
int
>
v(values, values +
sizeof
(values) /
sizeof
(int
));
// affichage du contenu du tableau
for_each (v.begin(), v.end(), [](int
i) {
cout <<
"i:"
<<
i <<
endl;}
);
Pour uniformiser la syntaxe, le C++11 introduit la possibilité d'utiliser les std::initializer_list‹T>. On peut alors créer une fonction utilisant directement ce type de paramètre :
void
cout_all(initializer_list<
int
>
l)
{
for_each (l.begin(), l.end(), [](int
i) {
cout <<
"i:"
<<
i <<
endl;}
);
}
cout_all( {
1
, 2
, 3
, 4
, 5
}
);
En créant un constructeur initializer-list dans une classe, on pourra donc initialiser cette classe avec une liste :
struct
A {
A(initializer_list<
int
>
l)
{
values.resize(l.size());
copy(l.begin(), l.end(), values.begin());
}
vector<
int
>
values;
}
;
// plusieurs syntaxes possibles
A a ( {
1
, 2
, 3
, 4
, 5
}
); // appel explicite du constructeur
A a =
{
1
, 2
, 3
, 4
, 5
}
; // conversion implicite du constructeur
A a {
1
, 2
, 3
, 4
, 5
}
; // version sans le signal =
Les conteneurs (vector, list, etc.), les chaînes de caractères (string) et les expressions régulières (regex) de la bibliothèque standard ont été mis à jour pour pouvoir utiliser directement cette syntaxe. Pour les structures plus complexes, il est possible d'emboîter plusieurs initializer-list :
vector<
int
>
values =
{
1
, 2
, 3
, 4
, 5
}
;
vector<
double
>
values =
{
1
, 2
, 3
, 4
, 5
}
; // conversion implicite int vers double
list<
pair<
string, int
>>
values =
{
{
"premier"
, 123
}
,
{
"second"
, 456
}
,
{
"dernier"
, 789
}
}
;
map<
string, vector<
int
>>
values =
{
{
"premier"
, {
1
, 2
, 3
}}
,
{
"second"
, {
4
, 5
, 6
}}
,
{
"dernier"
, {
7
, 8
, 9
}}
}
;
map<
int
, int
>
m;
m.insert({
1
, 2
}
); // insertion d'une paire d'éléments
Il est très important de faire attention à la syntaxe, pour éviter les confusions (et les erreurs donc) :
vector<
int
>
v1( 7
); // appel de vector::vector(size) donc v1 contient { 0, 0, 0, 0, 0, 0, 0 }
vector<
int
>
v2{
7
}
; // appel de vector::vector(initializer_list) donc v2 contient { 7 }
vector<
int
>
v3 =
7
; // erreur
vector<
int
>
v4 =
{
7
}
; // v4 contient { 7 }
vector<
int
>
v5 =
vector<
int
>
(7
); // v5 contient { 0, 0, 0, 0, 0, 0, 0 }
vector<
int
>
v6 =
vector<
int
>{
7
}
; // v6 contient { 7 }
vector<
int
>
v7;
v7 =
7
; // erreur
vector<
int
>
v8;
v8 =
{
7
}
; // v8 contient { 7 }
void
f(const
vector<
int
>&
);
f(7
); // erreur
f({
7
}
); // appel de f avec la liste { 7 }
Les range-based for permettent également d'utiliser cette syntaxe :
for
(const
auto
i : {
1
, 2
, 3
, 4
, 5
}
) cout <<
i <<
endl;
V-C. Les énumérations à typage fort▲
Norme C++ : N2347.
Les énumérations permettent une liste d'identifiants en leur attribuant automatiquement des valeurs par défaut, en commençant à 0. Par exemple, le code suivant permet de créer les identifiants Nord, Est, Sud et Ouest en leur attribuant respectivement les valeurs 0 à 3.
enum
Direction
{
Nord, // vaut 0
Est, // vaut 1
Sud, // vaut 2
Ouest // vaut 3
}
;
Dans le C++03, les énumérations sont faiblement typées et peuvent être implicitement transformées en entiers par le compilateur, ce qui permet de les manipuler comme des nombres pour faire des calculs ou des tests. Ainsi, le code suivant est accepté :
Direction d =
1
; // d est équivalent à "Est"
d +=
2
; // d est équivalent maintenant à "Ouest"
int
i =
d -
1
; // on peut convertir une énumération en entier
if
(i ==
Ouest)
cout <<
"On est à l'ouest !"
<<
endl;
Cependant, manipuler les énumérations comme des entiers n'a pas toujours un sens et la conversion implicite peut provoquer des erreurs dans le code.
Direction d =
Ouest +
1
; // erreur, 4 n'est pas une valeur acceptée
Le C++11 propose les énumérations fortement typées, qui ne sont pas implicitement converties en entiers. Elles s'utilisent en ajoutant le mot clé class devant le nom de l'énumération :
enum
class
Direction
{
Nord,
Est,
Sud,
Ouest
}
;
Pour utiliser cette énumération, il est alors nécessaire de préciser le nom de l'énumération devant la valeur. Il n'est pas possible de convertir automatiquement en entier, mais il est possible de le faire en utilisant une conversion explicite avec static_cast :
Direction d1 =
Nord; // erreur, Nord est inconnu
Direction d2 =
Direction::
Sud; // ok
Direction d3 =
2
; // erreur, pas de conversion implicite de int en Direction
int
i =
static_cast
<
int
>
(d2); // ok, conversion explicite de Directon en int
Les énumérations sont converties à la compilation dans un type entier. Par défaut, le type utilisé est un entier signé le plus petit capable de contenir toutes les valeurs de l'énumération. Lorsqu'on souhaite forcer l'utilisation d'un type à la place du type par défaut, il est possible d'ajouter une valeur dans l'énumération, correspondant à la valeur maximale que peut prendre le type que l'on désire imposer :
enum
class
IntDirection
{
Nord,
Est,
Sud,
Ouest,
FORCE_32BITS =
0xffffffff
}
;
Le C++11 introduit la définition explicite du type sous-jacent à une énumération classique ou forte. Le type à utiliser à la place par défaut est indiqué après le nom de l'énumération. Cela permet en plus d'utiliser des types non signés.
// énumération classique
enum
UnsignedShortDirection : unsigned
short
{
Nord,
Sud,
Ouest,
Est
}
;
// énumération forte
enum
class
StrongUnsignedShortDirection : unsigned
short
{
Nord,
Sud,
Ouest,
Est
}
;
La norme garantit également que les variables correspondant à cette énumération seront de même taille que le type sous-jacent :
Direction directionDuSoleil =
Sud;
static_assert
(sizeof
(directionDuSoleil) ==
sizeof
(unsigned
short
), "Not same size"
);
Il est possible de récupérer le type sous-jacent d'une énumération, par exemple dans une classe template dont l'énumération est passée en argument en utilisant la fonction std::underlying_type fournie par la bibliothèque standard type traits :
#include
<iostream>
#include
<type_traits>
enum
e1 {}
;
enum
class
e2: int
{}
;
int
main() {
bool
e1_type =
std::
is_same<
unsigned
,typename
std::
underlying_type<
e1>
::
type
>
::
value;
bool
e2_type =
std::
is_same<
int
,typename
std::
underlying_type<
e2>
::
type
>
::
value;
std::
cout
<<
"underlying type for 'e1' is "
<<
(e1_type?"unsigned"
:"non-unsigned"
) <<
'
\n
'
<<
"underlying type for 'e2' is "
<<
(e2_type?"int"
:"non-int"
) <<
'
\n
'
;
}
Manque : scope
V-D. Les nouveaux types de caractères▲
Norme C++ : N2249.
Pour ajouter la prise en charge des chaînes de caractères Unicode, le nouveau standard ajoute plusieurs nouvelles fonctionnalités. Pour commencer, les nouveaux types char16_t et char32_t sont ajoutés, ainsi que le nouveau fichier d'en-tête ‹cuchar>. De plus, les nouvelles chaînes et caractères littéraux suivants sont ajoutés :
// anciens caractères littéraux existants
'a'
; // caractère littéral ordinaire de type char
'
\t
'
; // littérale multicaractère de type char (représente une tabulation)
L'xxx'; // caractère de type wchar_t
// nouveaux caractères littéraux
u'xxx'; // caractère littéral de type char16_t
U'xxx'; // caractère littéral de type char32_t
// anciennes chaînes littérales existantes
"une chaîne de caractères"
; // chaîne littérale ordinaire de type const char[N]
L"une autre chaîne de caractères"
; // chaîne littérale de type const wchar_t[N]
// nouvelles chaînes littérales
u"encore une autre chaîne de caractères"
; // chaîne littérale de type const char16_t[N]
U"une dernière chaîne de caractères"
; // chaîne littérale de type const char32_t[N]
Les chaînes littérales sont concaténées que si elles ont le même préfixe ou que l'une des deux chaînes n'a pas de préfixe :
u"a"
u"b"
; // devient u"ab"
U"a"
"b"
; // devient U"ab"
Le type string, fourni par la STL est compatible avec les différents types de représentation des caractères (char, wchar_t, char16_t, char32_t). Pour tester la disponibilité de ces types, il est possible d'utiliser les macros __STDC_UTF_16__ et __STDC_UTF_32__ définis dans ‹cuchar>.
L'en-tête ‹cuchar> fournit également les fonctions de conversion de char en char16_t et char32_t et réciproquement : mbrtoc16, c16rtomb, mbrtoc32 et c32rtomb.
#include
<cuchar>
size_t std::
mbrtoc16(char16_t
*
pc16, const
char
*
s, size_t n, mbstate_t *
ps); // char en char16_t
size_t std::
c16rtomb(char
*
s, char16_t
c16, mbstate _t *
ps); // char16_t en char
size_t std::
mbrtoc32(char32_t
*
pc32, const
char
*
s, size_t n, mbstate_t *
ps); // char en char32_t
size_t std::
c32rtomb(char
*
s, char32_t
c32, mbstate_t *
ps); // char32_t en char
V-E. Les fonctions par défaut et supprimées▲
Norme C++ : N2346.
Dans la sémantique d'entité, il est nécessaire désactiver la copie. Par défaut, le compilateur crée automatiquement le constructeur par copie et l'opérateur d'affectation pour autoriser la copie. Pour éviter cela, il était habituel de rencontrer la syntaxe suivante, dans laquelle ces fonctions sont déclarées comme membres privés :
class
A {
private
:
A(const
A&
) {}
A&
operator
=
(const
A&
) {}
}
;
Pour simplifier l'écriture, le C++11 introduit le mot clé delete pour annuler la déclaration automatique de ces fonctions.
class
A {
A(const
A&
) =
delete
;
A&
operator
=
(const
A&
) =
delete
;
}
;
Lorsque l'on déclare un constructeur, le compilateur ne va pas générer automatiquement les constructeurs par défaut. Il faut donc les réécrire soi-même :
class
A {
A(const
B&
) {
... }
// nouveau constructeur quelconque
A() {
... }
// ajout du constructeur par défaut
A(const
A&
) {
... }
// ajout du constructeur par copie
}
;
Pour éviter d'avoir à réécrire du code trivial, le C++11 introduit le nouveau mot clé default permettant d'indiquer au compilateur qu'il doit générer les fonctions par défaut :
class
A {
A(const
B&
) {
... }
// nouveau constructeur quelconque
A() =
default
;
A(const
A&
) =
default
;
}
;
Le mot clé default peut être utilisé avec n'importe quelle fonction qui peut être générée automatiquement par le compilateur (son utilisation peut même être redondante). Le mot clé delete peut être utilisé avec n'importe quelle fonction. Il est ainsi possible d'éviter les conversions implicites des paramètres entrants :
struct
A {
A(long
long
);
}
;
struct
B {
B(long
long
);
B(long
) =
delete
;
}
;
long
long
ll =
123456789
;
long
l =
123456789
;
A a1(ll); // ok
A a2(l); // conversion implicite de long en long long
B b1(ll); // ok
B b2(l); // erreur, constructeur B(long) est effacé
V-F. Sizeof étendu▲
Norme C++ : N2253.
Dans le C++98, il n'est pas possible de faire référence dans sizeof à une donnée membre non statique sans instancier cette classe. Le C++11 supprime cette limitation.
struct
C {
static
T1 m1;
T2 m2;
void
foo() const
;
}
;
sizeof
(C::
m1); // ok, variable membre statique
sizeof
(C::
m2); // erreur en C++98, valide en C++11
C c;
sizeof
(c.m2); // ok, la classe est instanciée
void
C::
foo() const
{
sizeof
(m2); }
// erreur en C++98, valide en C++11
sizeof
(((C*
) 0
)->
m2); // hack possible pour contourner en C++98
V-G. Les espaces de noms inline (association d'espaces de noms)▲
Norme C++ : N2535.
Les espaces de noms inline sont un mécanisme permettant de faciliter l'évolution des bibliothèques en proposant une forme de versionning.
// module_v2.h
namespace
v2 {
void
f(int
); // nouvelle version d'une fonction
void
f(double
); // nouvelle fonction
// ...
}
// module_v1.h
namespace
v1 {
void
f(int
);
// ...
}
// module.h
namespace
MyModule {
#include
"module_v2.h"
#include
"module_v1.h"
using
v2::
f(int
); // déclaration
}
#include
"module.h"
MyModule::v1::
f(1
); // ancienne version de f()
MyModule::v2::
f(1
); // nouvelle version de f()
MyModule::
f(1
);
V-H. La propagation des exceptions▲
Norme C++ : N2179.
Pour faciliter la manipulation des exceptions en dehors des blocs catch, par exemple pour propager les exceptions entre les threads, la norme C++11 introduit un nouveau type et trois nouvelles fonctions :
namespace
std {
typedef
unspecified exception_ptr;
exception_ptr current_exception();
void
rethrow_exception( exception_ptr p );
template
<
class
E >
exception_ptr copy_exception( E e );
}
Le type exception_ptr fait référence à une exception, une copie d'une exception ou vers nullptr s'il n'y a pas d'exception en cours (attention, exception_ptr n'est pas un pointeur et ne peut pas être déréférencé). On peut récupérer l'exception courante en utilisant la fonction current_exception dans un bloc catch. Les deux autres fonctions, comme leurs noms l'indiquent, permettent de relancer une exception (dans un bloc throw donc, pour pouvoir la recapturer) et de copier une exception.
#include
<iostream>
#include
<string>
#include
<exception>
#include
<stdexcept>
void
handle_exception(std::
exception_ptr e)
{
try
{
if
(e !=
nullptr
) {
// ou : e != std::exception_ptr()
std::
rethrow_exception(e); // relance l'exception
}
}
catch
(const
std::
exception&
e) {
std::
cout <<
"Exception attrapée : "
<<
e.what() <<
std::
endl;
}
}
int
main()
{
std::
exception_ptr e;
try
{
std::
string().at(1
); // génère une exception
}
catch
(...) {
e =
std::
current_exception(); // on récupère l'exception
}
handle_exception(e); // on peut manipuler l'exception en dehors du catch
}
Ces nouvelles fonctionnalités vont prendre tout leur sens lorsque l'on travaillera dans une application multithread. Sans cela, lorsqu'une exception est lancée dans un thread, elle devra être traitée dans le thread lançant cette exception :
// THREAD SECONDAIRE
#include
<iostream>
#include
<string>
#include
<exception>
#include
<stdexcept>
void
handle_exception(std::
exception_ptr e)
{
try
{
if
(e !=
nullptr
) {
// ou : e != std::exception_ptr()
std::
rethrow_exception(e); // relance l'exception
}
}
catch
(const
std::
exception&
e) {
std::
cout <<
"Exception attrapée : "
<<
e.what() <<
std::
endl;
}
}
// THREAD PRINCIPAL
int
main()
{
std::
exception_ptr e;
try
{
std::
string().at(1
); // génère une exception
}
catch
(...) {
e =
std::
current_exception(); // on récupère l'exception
}
handle_exception(e); // on peut manipuler l'exception en dehors du catch
}
Avec ce mécanisme de propagation des exceptions, il est donc possible de donner la responsabilité de traitement des exceptions au code client dans un environnement multithread.
VI. Remerciements▲
Sources :
- documentation de gcc 4.7 ;
- FAQ C++11 sur le site de Bjarne Stroustrup ;
- Overview of the new C++0x de Scott Meyers (2010).
Remarque : le code est directement issu de la documentation de gcc, utilisable selon les termes de la licence consultable à la fin de cette page, ainsi que des drafts du comité de normalisation dont les liens sont donnés.
Merci à Winjerome pour sa relecture orthographique