Sommaire
Les évolutions du langage
La délégation de constructeur
Il est désormais possible d'appeler un constructeur dans un autre (du même objet), cela permet d'écrire plus facilement des contructeurs par défaut. Par exemple, le code suivant :
class MyClass {
private:
constructor(int num);
public:
MyClass(int num) {constructor(num);};
MyClass() {constructor(42);};
}
pourra être remplacé par :
class MyClass {
public:
MyClass(int num);
MyClass() {MyClass(42)};
}
L'inférence de type
L'inférence de types est un mécanisme qui permet à un compilateur ou un interpréteur de rechercher automatiquement les types associés à des expressions, sans qu'ils soient indiqués explicitement dans le code source. Elle est disponible via deux moyen. D'abord avec le mot-clef auto
quand on déclare une variable, cette dernière prendra le type de son assignation. Cela donne :
auto maVariable = 5;
Et la variable aura le type int
. Bien sûr, ce n'est pas à utiliser dans n'importe quel cas et l'utilisation la plus courante sera sans doute lors du parcours d'une liste dans une boucle :
for (std::vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)
sera remplacé par :
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)
Une autre utilisation de l'inférence de type se fait avec le mot-clef en:decltype. Cela permet de typer une variable non pas avec le type de l'assignation mais avec celui d'une autre variable :
char a;
decltype(a) b=5;
Dans ce cas, b
sera du type char
. L'utilisation devient intéressante lorsqu'on utilise une variable avec auto
ou lors de l'utilisation importante de la surcharge d'opérateur et des types spécialisés.
Le foreach
Il est désormais possible de parcourir une liste d'élément assez simplement :
int my_array[5] = {1, 2, 3, 4, 5};
for (int &x : my_array) {
x *= 2;
}
Cette forme est utilisable avec les tableaux de style C et tous les objets qui possèdent les méthodes begin()
et end()
renvoyant un itérateur.
Les fonctions lambdas
Les fonctions lambdas sont des fonctions anonymes qui peuvent être définies au mileu d'une autre fonction. Elle sont utiles pour définir une action sans devoir renvoyer le lecteur et l'auteur du code à un autre endroit. Par exemple, avec la fonction std::find
, il peut être utile de définir directement le prédicat plutôt que d'indiquer le nom d'une fonction dont le contenu est inconnu.
Les fonctions lambdas s'utilisent de la manière suivante :
[](int x, int y) { return x + y; }
Le type du retour peut être explicitement défini :
[](int x, int y) -> int { int z = x + y; return z; }
Les listes d'initialisations
Les listes d'initialisations permettent d'initialiser les objets en passant une liste de variable entre accolades, comme pour les tableaux et les structures. Pour que cela fonctionne, il faut définir un constructeur qui prend un seul argument de type std::initializer_list
. Concrètement cela donne :
class SequenceClass {
public:
SequenceClass(std::initializer_list<int> list);
};
Et cela s'utilise :
SequenceClass some_var = {1, 4, 5, 6};
Et cela peut même s'utiliser avec une fonction.
Le pointeur NULL
Pour comprendre son intérêt, il faut savoir que la constante NULL
est, la plupart du temps, définie comme 0, qui est de type int
, ce qui peut poser des problèmes lors de l'appel de méthodes surchargées. Il a donc été décidé d'introduire nullptr
qui peut avoir comme type n'importe quelle valeur de pointeur ou un booléen mais pas un autre type :
char *pc = nullptr; // OK
int *pi = nullptr; // OK
bool b = nullptr; // OK. b est faux.
int i = nullptr; // KO
Ceci permet lorsqu'on a deux fonctions telles que :
void foo(char *);
void foo(int);
que lorsque l'appel suivant est écrit :
foo(nullptr);
le compilateur choisisse la fonction foo(char *)
au lieu de foo(int)
, ce qui est le comportement le plus souvent désiré.
Les chaînes de caractères
Afin d'améliorer la prise en charge de l'unicode, des nouveaux préfixes ont été introduits :
u8"Une chaîne UTF-8"
u"Une chaîne UTF-16"
U"Une chaîne UTF-32"
Pour cela, les types char16_t et char32_t ont été créés. Il est aussi possible d'insérer un caractère unicode en tapant son code en hexadécimal et le préfixant de \u, par exemple \u2603.
Les chaînes raw ont aussi été ajoutées, elle permettent d'éviter de devoir échapper les caractères :
R"(Un slash \ au milieu)"
R"delimiteur(Un slash \ et des guillemets " au milieu )delimiteur"
(Attention, la coloration syntaxique n'est pas encore prévue pour ce nouveau standard).
Comme on le voit, il est possible de définir son propre délimiteur pour ces chaînes afin de pouvoir mettre n'importe quel texte.
Les évolutions de la bibliothèque standard
Les threads
Les threads sont pris officiellement en charge par la bibliothèque standard via l'objet std::thread
. Afin de pouvoir les utiliser efficacement, les mutex, variables conditionnelles, les tâches futures et les opérations atomiques sont aussi disponibles.
On peut quand même regretter l'absence de sémaphore dans la spécification.
Containers
Les tuples
Le tuple, ou n-uplets, est une collection ordonnée de n objets. Cette structure est désormais dipsonible et s'utilise de la manière suivante :
typedef std::tuple <int, double, long &, const char *> test_tuple;
long lengthy = 12;
test_tuple proof (18, 6.5, lengthy, "Ciao!");
lengthy = std::get<0>(proof); // Assign to 'lengthy' the value 18.
std::get<3>(proof) = " Beautiful!"; // Modify the tuple’s fourth element.
Les tables de hachage
Quatre nouveaux containers ont été ajoutés :
std::unordered_set
std::unordered_multiset
std::unordered_map
std::unordered_multimap
Les versions multi acceptent des entrées avec des clefs identiques. Les set sont des ensembles d'objets non-ordonnés tandis que les map font correspondre à chaque entrée une clef.
Les expressions régulières
Les expressions régulières font leur entrée dans la bibliothèques standard. La fonction std::regex_search
est utilisée pour chercher une correspondance tandis que la fonction std::regex_replace
sert à remplacer des occurrences dans la chaîne.
const char *reg_esp = R"([ ,.\t\n;:])"; // Une liste de caractères de séparation
// on utilise les chaîne raw pour éviter de doubler les antislash
std::regex rgx(reg_esp);
std::cmatch match; // match est utilisé pour stocker le résultat
const wchar32_t *target = u"Université de l'invisible - Ankh-Morpork";
// Identifie tous les mots de 'target' séparés par les caractères de 'reg_esp'.
if( std::regex_search( target, match, rgx ) ) { // Si des mots séparés par les caractères sont présent.
const size_t n = match.size();
for( size_t a = 0; a < n; a++ ) {
std::string str( match[a].first, match[a].second );
std::cout << str << std::endl;
}
}
Gestion des nombres pseudo-aléatoires
Cette nouvelle version du langage introduit une gestion supplémentaire des nombres pseudo-aléatoires, en remplacement de la vénérable fonction rand()
que C++ avait conservé du C.
Un objet de génération de nombre pseudo-aléatoire est composé de deux mécanismes :
- un générateur qui produit les nombres pseudo-aléatoires ;
- un distributeur, qui détermine la série et la distribution mathématique du résultat.
Contrairement à la fonction standard C rand
, le mécanisme du C++1x fournit trois algorithmes de génération :
linear_congruential
, ayant une qualité de génération et une rapidité moyenne, mais est très économe en mémoire ;
subtract_with_carry
, rapide mais de qualité moyenne ;
mersenne_twister
, de bonne qualité et très rapide, mais a l'inconvénient d'être plus gourmand en mémoire.
En revanche, un nombre appréciable de distributeurs existe : uniform_int_distribution
, bernoulli_distribution
(à ne pas confondre avec berlusconi_distribution
), geometric_distribution
, poisson_distribution
, binomial_distribution
, uniform_real_distribution
, exponential_distribution
, normal_distribution
, et gamma_distribution
.
Un exemple d'utilisation :
#include
#include
std::uniform_int_distribution<int> distribution(0, 99);
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(distribution, engine);
int random = generator(); // Génère un entier entre 0 et 99.
int random2 = distribution(engine); // Génère un autre entier en utilisant directement le générateur avec le moteur de distribution.
L'utilisation de ces objets permet de ne pas laisser à l'application le soin de s'occuper de l'initialisation de la graine, ou de mal l'initialiser, ce qui est source de nombreuses failles de sécurité.
Cet article est, en partie, une traduction de l'article Wikipedia C++0x