... | ... | @@ -11,6 +11,8 @@ entendre les mots "Studio" et "Microsoft", il s'agit d'un produit qui se veut ou |
|
|
rassurant de voir qu'il est contruit autour de technologies de dernière génération. Le support du debugging est
|
|
|
encore limité à trop peu de plateformes, mais la communauté semble très active.
|
|
|
|
|
|
---
|
|
|
|
|
|
## Initialisation uniforme
|
|
|
|
|
|
Il existe maintenant une nouvelle façon de donner une valeur initiale à une variable,
|
... | ... | @@ -225,15 +227,380 @@ int main() |
|
|
}
|
|
|
```
|
|
|
|
|
|
## Améliorations sur classes et objets
|
|
|
---
|
|
|
|
|
|
## Constructeurs délégués
|
|
|
|
|
|
On peut maintenant demander qu'un constructeur en utilise un autre, en employant
|
|
|
la même syntaxe que pour l'appel d'un constructeur d'une classe ascendante :
|
|
|
|
|
|
```c++
|
|
|
class A
|
|
|
{
|
|
|
public :
|
|
|
A() : A(0,0) { indic_ = false ; } // utilise A(int,int)
|
|
|
A( int x ) : A(x,0) { indic_ = false ; } // utilise A(int,int)
|
|
|
A( int x, int y ) { x_ = x ; y_ = y ; indic_ = true ; }
|
|
|
private :
|
|
|
int x_, y_ ;
|
|
|
bool indic_ ;
|
|
|
} ;
|
|
|
```
|
|
|
|
|
|
## Initialisation de données membres
|
|
|
|
|
|
C++03 ne permet pas d'initialiser des données membres lors de leur déclaration,
|
|
|
à l'exception des membres statiques constants. Cela devient possible pour les
|
|
|
membres non statiques avec C++11 :
|
|
|
|
|
|
```c++
|
|
|
class A
|
|
|
{
|
|
|
.....
|
|
|
private :
|
|
|
int n = 5 ; // interdit en C++03, OK en C++11
|
|
|
} ;
|
|
|
```
|
|
|
|
|
|
Il s'agit d'une initialisation par défaut qui pourra être éventuellement modifiée par
|
|
|
un constructeur.
|
|
|
|
|
|
Seule la notation "nue" ou par accolades est autorisée dans ce contexte : pas
|
|
|
d'initialisation par parenthèses.
|
|
|
|
|
|
Les membres statiques non constants ne peuvent toujours pas être
|
|
|
initialisés de cette manière.
|
|
|
|
|
|
## Héritage du constructeur
|
|
|
|
|
|
Les règles relatives à la redéfinition des fonctions membres font que, par défaut,
|
|
|
une fonction membre redéfinie dans une classe dérivée masque les fonctions de même
|
|
|
nom des classes ascendantes. Il reste toutefois possible de réintroduire ces fonctions
|
|
|
à l'aide de l'instruction `using`. Cependant, rien de comparable n'était prévu
|
|
|
pour les constructeurs en C++ ancien. Cela devient possible en C++11 :
|
|
|
|
|
|
```c++
|
|
|
class A
|
|
|
{
|
|
|
public :
|
|
|
A( int ) { ..... }
|
|
|
A( int, int ) { ..... }
|
|
|
} ;
|
|
|
class B : public A
|
|
|
{
|
|
|
using A::A ; // on peut utiliser A::A( int) et A::A( int, int )
|
|
|
// comme si ils étaient déclarés B::B( int) et B::B( int, int )
|
|
|
} ;
|
|
|
```
|
|
|
|
|
|
## Fonctions membres interdites
|
|
|
|
|
|
Il y a toute une catégorie d'objets pour lesquels autoriser les copies
|
|
|
n'a pas de sens (mutex, verrous, pointeurs propriétaires, ...). Pour
|
|
|
empêcher ces copies en C++03, on a pris l'habitude de déclarer privés
|
|
|
le constructeur par copie et l'affectation par copie, et de ne pas
|
|
|
fournir d'implémentation. Ainsi, toute tentative de copie depuis
|
|
|
l'extérieur de la classe déclenche une erreur de compilation, et toute
|
|
|
tentative depuis l'intérieur de la classe va déclencher une erreur
|
|
|
lors de l'édition de liens.
|
|
|
|
|
|
```c++
|
|
|
class no_copies
|
|
|
{
|
|
|
public :
|
|
|
no_copies() {}
|
|
|
private :
|
|
|
no_copies( no_copies const & ) ; // (1) No implementation
|
|
|
no_copies & operator=( no_copies const & ) ;
|
|
|
} ;
|
|
|
no_copies a ;
|
|
|
no_copies b(a) ; // (2) Won’t compile
|
|
|
```
|
|
|
|
|
|
En C++11, le comité a voulu remplacer cette astuce par un mécanisme
|
|
|
plus clair et plus général : la possibilité d'interdire une fonction en la
|
|
|
déclarant `= delete`. On peut ainsi réécrire la classe `no_copies` :
|
|
|
|
|
|
```c++
|
|
|
class no_copies
|
|
|
{
|
|
|
public :
|
|
|
no_copies() {}
|
|
|
no_copies( no_copies const & ) = delete ;
|
|
|
no_copies & operator=( no_copies const & ) = delete ;
|
|
|
} ;
|
|
|
```
|
|
|
|
|
|
L'intention est mieux exprimée, permet d'obtenir des messages plus clair
|
|
|
du compilateur, et une utilisation interdite depuis l'intérieur de la
|
|
|
classe est détectée dès la compilation.
|
|
|
|
|
|
Par ailleurs, les fonctions interdites prennent part à la résolution de la surcharge,
|
|
|
ce qui permet d'interdire certaines variantes, et certaines conversions implicites non
|
|
|
souhaitées. Par exemple, si on définit une fonction qui réclame une valeur `short`,
|
|
|
on peut facilement empêcher le compilateur d'accepter (et raccourcir) une valeur
|
|
|
`int` :
|
|
|
|
|
|
```c++
|
|
|
void foo( short ) ;
|
|
|
void foo( int ) = delete ;
|
|
|
.....
|
|
|
foo(42) ; // Error, int overload declared deleted
|
|
|
foo((short)42) ; // OK
|
|
|
```
|
|
|
|
|
|
## Fonctions par défaut
|
|
|
|
|
|
A l'opposé de ce qui précède, le mot-clé `default` permet d'exiger explicitement
|
|
|
du compilateur qu'il fournisse son implémentation par défaut pour les fonctions qu'il
|
|
|
sait générer, telles que les constructeurs vides, par copie et déplacement, les
|
|
|
destructeurs et les opérateurs d'affectation par copie et déplacement.
|
|
|
|
|
|
Dans quel but ?
|
|
|
* Pour les faire apparaitre clairement dans l'interface, à des fins de documentation.
|
|
|
* Pour les placer en accès protégé ou privé, alors qu'il est public par défaut.
|
|
|
* Pour faire en sorte que le constructeur prenne un original no constant.
|
|
|
* Pour rendre un destructeur virtuel, totu en conservant son implementation par defaut.
|
|
|
* Pour forcer le compilateur à générer la fonction, dans un contexte ou il ne le fait
|
|
|
pas habituellement. Par exemple pour conserver le constructeur par defaut, alors
|
|
|
qu'on fournit par ailleurs un autre constructeur.
|
|
|
* Pour préserver les propriétés particulières liées aux implémentations par défaut.
|
|
|
|
|
|
```c++
|
|
|
class Y
|
|
|
{
|
|
|
private :
|
|
|
Y() = default ; // Change access
|
|
|
public :
|
|
|
Y( Y & ) = default ; // Take a non-const reference
|
|
|
T & operator=( const Y & ) = default ; // Declare as defaulted for documentation
|
|
|
protected :
|
|
|
virtual ~Y() = default ; // Change access and add virtual
|
|
|
} ;
|
|
|
```
|
|
|
|
|
|
Parmi les propriétés des implémentations par défaut, le fait qu'elles puissent
|
|
|
être "triviales" peut avoir des effets :
|
|
|
* Un objet dont le constructeur par copie, l'affectation par copie et le destructeur
|
|
|
sont triviaux, peut être copié avec memcpy ou memmove (très rapide).
|
|
|
* Pour qu'un type littéral puisse être utilisé dans une fonction constante (constexpr),
|
|
|
il faut que son constructeur vide, son constructeur par copie et son destructeur
|
|
|
soient triviaux.
|
|
|
* Une classe possédant un constructeur vide trivial, ainsi qu'un constructeur par copie,
|
|
|
un opérateur d'affectation par copie et un destructeur triviaux, peut être utilisé
|
|
|
dans une union aux constructeur et destructeur personnalisés.
|
|
|
* Une classe possédant un opérateur d'affectation par copie peut être utilisé
|
|
|
avec le patron de classe `std::atomic`.
|
|
|
|
|
|
Une implémentation n'est pas triviale simplement parce qu'elle est fournit par le
|
|
|
compilateur. Il y a d'autres conditions. Mais si elle est fournie par le développeur,
|
|
|
elle ne peut pas être triviale.
|
|
|
|
|
|
Par ailleurs, une classe dont aucun constructeur n'est implémenté par le développeur
|
|
|
peut être un "aggrégat" et être initialisée à l'aide d'un initialisateur d'aggrégat :
|
|
|
|
|
|
```c++
|
|
|
struct aggregate
|
|
|
{
|
|
|
aggregate() = default ;
|
|
|
aggregate( aggregate const & ) = default ;
|
|
|
int a ;
|
|
|
double b ;
|
|
|
} ;
|
|
|
aggregate x = {42,3.141} ; // x.a <= 42, x.b <= 3.141
|
|
|
```
|
|
|
|
|
|
## Test des noms de membres d'une classe dérivée
|
|
|
|
|
|
En C++03, on peut redéfinir involontairement une fonction virtuelle. On peut aussi,
|
|
|
suite à une faute de frappe dans une redéfinition de fonction virtuelle, aboutir à la
|
|
|
définition d'une nouvelle fonction.
|
|
|
|
|
|
C++11 permet de se protéger de ces erreurs : si vous qualifiez une méthode virtuelle
|
|
|
à l'aide de ```override```, le compilateur vérifiera que la méthode existe bel et
|
|
|
bien dans la classe de base.
|
|
|
|
|
|
Par ailleurs, si vous voulez interdire qu'une méthode virtuelle , bien que virtuelle,
|
|
|
soit redéfinie dans une classe dérivée, utilisez le qualificateur ```final```. Ce même
|
|
|
qualificateur peut aussi servir à dire qu'une classe ne peut plus être dérivée.
|
|
|
|
|
|
```c++
|
|
|
class A
|
|
|
{
|
|
|
public :
|
|
|
virtual void fct1() =0 ;
|
|
|
virtual void fct2( int ) =0 ;
|
|
|
virtual void fct3( bool ) =0 ;
|
|
|
} ;
|
|
|
|
|
|
class B : public A
|
|
|
{
|
|
|
public :
|
|
|
virtual void fct3( bool ) final ;
|
|
|
} ;
|
|
|
|
|
|
class C final : public B
|
|
|
{
|
|
|
public :
|
|
|
void fct1 () override ; // OK
|
|
|
void ft2 ( int ) override ; // erreur : A::ft2 n'existe pas
|
|
|
void fct2 ( bool ) override ; // erreur : pas les bons types
|
|
|
void fct3 ( bool ) override ; // erreur : B::fct3 est finale
|
|
|
} ;
|
|
|
|
|
|
class D : public C // erreur : C est final
|
|
|
{} ;
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
## Assertions statiques
|
|
|
|
|
|
Depuis C++11, la nouvelle directive `static_assert` permet de verifier une condition
|
|
|
**lors de la compilation** et d'afficher le message de son choix en cas d'échec. Elle
|
|
|
est typiquement utilisée pour tester un type ou un entier utilisé dans un patron :
|
|
|
|
|
|
```c++
|
|
|
template <typename T> .....
|
|
|
static_assert(sizeof(T)>=8,"Type de taille insuffisante\n") ;
|
|
|
```
|
|
|
|
|
|
Un autre exemple, indépendant des patrons :
|
|
|
|
|
|
```c++
|
|
|
const int n = 5 ;
|
|
|
.....
|
|
|
static_assert(n>0,"Dimension incorrecte pour le tableau t\n") ;
|
|
|
double t[n] ;
|
|
|
```
|
|
|
|
|
|
## Nouvelles expressions constantes
|
|
|
|
|
|
## Références à temporaires
|
|
|
En C++ ancien, les littéraux entiers tels que 42 sont des expressions constantes.
|
|
|
Le sont aussi des expressions arithmétiques simples telles que 23*2-4. On peut
|
|
|
également y inclure des variables constantes entières, si elles sont elle-même
|
|
|
initialisées avec une expression constante.
|
|
|
|
|
|
```c++
|
|
|
const int i = 23 ;
|
|
|
const int two_i = i * 2 ;
|
|
|
const int four = 4 ;
|
|
|
const int forty_two = two_i - four ;
|
|
|
```
|
|
|
|
|
|
Certaines situations du C++ requièrent une expression constante :
|
|
|
* la taille d'un tableau statique,
|
|
|
```c++
|
|
|
int bounds = 99 ;
|
|
|
int array[bounds] ; // Error bounds is not a constant expression
|
|
|
const int bounds2 = 99 ;
|
|
|
int array2[bounds2] ; // OK, bounds2 is a constant expression
|
|
|
```
|
|
|
* initialiser un paramètre de patron qui n'est pas un type,
|
|
|
```c++
|
|
|
template<unsigned size>
|
|
|
struct test
|
|
|
{} ;
|
|
|
test<bounds> ia ; // Error bounds is not a constant expression
|
|
|
test<bounds2> ia2 ; // OK, bounds2 is a constant expression
|
|
|
```
|
|
|
* initialiser un membre constant statique au sein d'une classe,
|
|
|
```c++
|
|
|
class X
|
|
|
{ static const int the_answer = forty_two ; } ;
|
|
|
```
|
|
|
* initialiser statiquement une variable globale de type prédéfini ou aggrégat
|
|
|
```c++
|
|
|
struct my_aggregate
|
|
|
{ int a ; int b ; } ;
|
|
|
static my_aggregate ma1 = { forty_two, 123 } ; // Static initialization
|
|
|
int dummy = 257 ;
|
|
|
static my_aggregate ma2 = { dummy, dummy } ; // Dynamic initialization
|
|
|
```
|
|
|
|
|
|
Le C++ moderne a largement étendu ce qui constitue des expressions constantes,
|
|
|
avec l'introduction du mot clé `constexpr`, qui signifie grosso modo
|
|
|
"constante précalculée à la compilation". On peut, par exemple, utiliser à présent
|
|
|
les types prédéfinis flottants :
|
|
|
|
|
|
```c++
|
|
|
constexpr double pi = 3.1415926535897 ;
|
|
|
class X { static constexpr double deux_pi__ = 2*pi ; } ;
|
|
|
```
|
|
|
En fait, nous pouvons maintenant utiliser tous les types dits "littéraux".
|
|
|
Pour tenter de faire simple, il s'agit des types prédéfinis, et de toute classe
|
|
|
qui possède un constructeur par défaut, un destructeur par défaut non virtuel,
|
|
|
et dont toutes les données membres et classes de base sont elle-mêmes littérales
|
|
|
(notons que std::string ne remplit ces conditions).
|
|
|
|
|
|
## Fonctions `constexpr`
|
|
|
|
|
|
On peut également faire intervenir des fonctions (libres ou membre d'une classe
|
|
|
littérale), à condition que leur corps remplisse certaines conditions. A l'origine, en
|
|
|
C++11, le corps devait se limiter à une seule instruction `return` (ce qui amenait
|
|
|
le recours à l'opérateur `?:` pour émuler un `if`, ou à des appels récursifs pour
|
|
|
émuler un `for`).
|
|
|
|
|
|
```c++
|
|
|
constexpr int square(int x)
|
|
|
{ return x*x ; }
|
|
|
|
|
|
int array[square(5)] ;
|
|
|
```
|
|
|
|
|
|
Le fait qu'une qu'une fonction soit déclarée `constexpr` ne garantit pas
|
|
|
qu'elle produit des expressions constantes. Elle peut également être appelée
|
|
|
avec des arguments d'entrée ordinaires, auquel cas elle renvoie un résultat ordinaire.
|
|
|
Ci-dessous, l'appel à `square` est parfaitement valide, mais le résultat ne peut pas
|
|
|
servir de taille de tableau.
|
|
|
|
|
|
```c++
|
|
|
int dummy = 4 ;
|
|
|
int array[square(dummy)] ; // (1) Error, dummy is not a constant expression
|
|
|
```
|
|
|
|
|
|
Aujourd'hui, en C++14, on peut avoir des corps de fonctions `constexpr` beaucoup
|
|
|
plus complexes, mais en continuant de s'interdire tout ce qui peut gêner
|
|
|
une évaluation à la compilation :
|
|
|
* tous les types impliqués doivent être littéraux,
|
|
|
* les variables locales doivent être initialisées,
|
|
|
* on s'interdit tout accès à une variable extérieure, globale, statique,
|
|
|
* on s'interdit de traiter les exceptions,
|
|
|
* on appelle que des fonctions elles-mêmes `constexpr,
|
|
|
* les fonctions membres ne doivent pas être virtuelles.
|
|
|
|
|
|
ATTENTION : à propos des fonctions membres d'une classe, en C++11 le mot
|
|
|
clef `constexpr` impliquait un `const` implicite ; ce n'est plus le
|
|
|
cas en C++14.
|
|
|
|
|
|
Quand il est appliqué à des patrons, le qualificateur `constexpr` est ignoré
|
|
|
si l'un des paramètres utilisé pour les arguments ou le retour n'est pas un type
|
|
|
littéral. Vous pouvez ainsi écrire des fonctions qui peuvent être `constexpr```
|
|
|
ou non, selon les paramètres utilisés.
|
|
|
|
|
|
```c++
|
|
|
template<typename T>
|
|
|
constexpr T sum(T a,T b)
|
|
|
{
|
|
|
return a+b;
|
|
|
}
|
|
|
|
|
|
constexpr int i=sum(3,42); // OK, sum<int> is constexpr
|
|
|
|
|
|
std::string s =
|
|
|
sum(std::string("hello"),
|
|
|
std::string(" world")); // OK, but sum<std::string> isn’t constexpr
|
|
|
```
|
|
|
|
|
|
Au final, tout ceci peut sembler beaucoup d'efforts pour définir une taille de tableau
|
|
|
ou une valeur initiale constante, mais l'économie de temps d'exécution n'est pas
|
|
|
le seul bénéfice : grâce à la pré-évaluation par le compilateur, on échappe
|
|
|
aux problème de l'ordre d'initialisation des variables globales au lancement d'un
|
|
|
programme. Et dans un contexte multi-thread, on échappe aux aléas d'exécution des
|
|
|
threads, ce qui est particulièrement important pour des classes telles que std::mutex
|
|
|
ou std::atomic.
|
|
|
|
|
|
---
|
|
|
|
|
|
## Inférence de type
|
|
|
|
... | ... | @@ -320,6 +687,10 @@ int main() |
|
|
```
|
|
|
## Fonctions lambdas
|
|
|
|
|
|
---
|
|
|
|
|
|
## Références à temporaires
|
|
|
|
|
|
## Nouveaux pointeurs améliorés
|
|
|
|
|
|
## Autres nouveautés de la bibliothèque standard
|
... | ... | |