C++11
C++11 | |
Date de première version | |
---|---|
Influencé par | C++ Technical Report 1 (en) |
Site web | www.iso.org/standard/50372.html |
modifier |
C++11, anciennement connu sous le nom de C++0x[1], est une norme pour le langage C++ en informatique. Elle a été approuvée unanimement le [2]. Elle remplace la précédente norme, ISO/CEI 14882, publiée en et mise à jour en . Ces dernières sont plus connues sous les noms informels de C++98 et C++03. C++11 introduit plusieurs nouveautés au langage initial, ainsi que de nouvelles fonctionnalités à la bibliothèque standard du C++ comme la plupart des bibliothèques du Technical Report 1, à l'exception de la bibliothèque de fonctions mathématiques spéciales.
C++11 a été publié sous le nom de ISO/CEI 14882:2011 en . Une version payante est disponible sur le site de l'ISO[3]. Le dernier working draft gratuit est le N3337, qui date du , les seules différences avec le standard étant des corrections éditoriales.
Un langage de programmation comme le C++ suit une évolution qui permet aux programmeurs de coder plus rapidement, de façon plus élégante et permettant de faire du code maintenable. Ce processus soulève inévitablement des questions de compatibilité avec le code existant, ce qui s'est produit de temps en temps pendant le processus de développement du C++. Cependant, d'après l'annonce faite par Bjarne Stroustrup, inventeur du langage C++ et membre du comité, la nouvelle norme est presque totalement compatible avec la norme précédente.
Changements prévus pour la mise à jour de la norme
[modifier | modifier le code]Les changements du langage C++ concernent aussi bien le langage initial que la bibliothèque standard. Durant le développement de chaque fonctionnalité de la nouvelle norme, le comité a appliqué les directives suivantes :
- Garder la stabilité et la compatibilité avec le C++98 et, si possible, avec le langage C.
- Préférer l'introduction de nouvelles fonctionnalités par la bibliothèque standard, plutôt que par le langage lui-même.
- Préférer les changements qui peuvent faire évoluer les techniques de programmation.
- Améliorer le C++ pour faciliter la mise en place de systèmes et de bibliothèques, plutôt qu'introduire de nouvelles fonctionnalités seulement utiles pour des applications spécifiques.
- Augmenter la protection des types en fournissant des alternatives plus sécurisées que les actuelles, plutôt non sécurisées.
- Augmenter les performances et les capacités à travailler directement avec le matériel.
- Proposer des solutions propres aux problèmes actuels.
- Implémenter le principe du « zero-overhead » (on ne paye le coût d'une fonctionnalité que si l'on s'en sert).
- Rendre le C++ facile à apprendre et à enseigner sans enlever les fonctionnalités requises par les programmeurs experts.
Extensions du langage
[modifier | modifier le code]Multitâche
[modifier | modifier le code]La mémoire locale de thread ou Thread Local Storage n'est pas un concept inventé par la nouvelle norme : de nombreux compilateurs proposent déjà cette fonctionnalité, ainsi que la bibliothèque threads de Boost. C++11 introduit le mot-clef thread_local
pour déclarer qu'une variable doit être stockée dans une zone mémoire appartenant au thread. Chaque thread embarque ainsi sa propre copie d'une variable déclarée de la sorte, et les modifications d'une de ces copies n'affectent pas les copies appartenant aux autres threads.
Lorsqu'une variable statique (ou une variable de classe) est ainsi définie, sa durée de vie est alors réduite à celle du thread (le destructeur des objets, notamment, est appelé lorsque le thread s'achève).
Les classes
[modifier | modifier le code]Délégation du constructeur [4]
[modifier | modifier le code]En C++03, un constructeur appartenant à une classe ne peut pas appeler un autre constructeur de cette même classe, ce qui peut entraîner de la duplication de code lors de l'initialisation de ses attributs. En permettant au constructeur de déléguer la création d'une instance à un autre constructeur, C++11 apporte donc une solution.
class une_classe {
int nombre;
public:
une_classe(int nouveau_nombre) : nombre(nouveau_nombre) {}
une_classe() : une_classe(42) {}
};
Dans l'exemple ci-dessus, on peut voir que le second constructeur appelle le premier constructeur, ce qui aurait conduit à une erreur de compilation en C++03.
Héritage du constructeur [5]
[modifier | modifier le code]En C++03, les constructeurs d'une classe de base ne sont pas hérités par ses classes dérivées. C++11 permet d'hériter explicitement des constructeurs de la classe de base grâce à l'instruction using
class classe_base {
public:
classe_base(int nombre) : m_nombre(nombre)
private:
int m_nombre;
};
class classe_derive : public classe_base {
public:
using classe_base::classe_base;
};
Initialiseurs d'attributs
[modifier | modifier le code]En C++03, il est possible d'assigner une valeur par défaut aux variables constantes directement dans le fichier d'en-tête. C++11 étend cette possibilité aux attributs des classes. Par exemple il est désormais tout à fait possible d'écrire:
class UneClasse {
public:
UneClasse() {}
explicit UneClasse(int valeur) : m_valeur(valeur) {}
private:
int m_valeur = 5;
};
Dans ce code, tous les constructeurs de la classe vont initialiser m_valeur
à 5, si le constructeur ne remplace pas l'initialisation avec la sienne. Par exemple, le constructeur vide ci-dessus va initialiser m_valeur
à 5 selon la définition de la classe, mais le constructeur qui prend un int
en paramètre initialisera m_valeur
à ce paramètre. Il est également possible d'utiliser d'autres attributs dans l'initialisation, et d'utiliser un constructeur ou l'utilisation uniforme au lieu de l'initialisation par assignation.
Sizeof sur les attributs de classes sans objet explicite
[modifier | modifier le code]En C++03, sizeof
peut être utilisé sur des types ou des objets, mais pas sur un membre de classe (excepté dans la bibliothèque Qt).
C++11 le rend possible. On peut donc maintenant faire :
struct UnType { UnAutreType membre; };
sizeof(UnType::membre); // Fonctionne en C++11, mais pas en C++03
Liste d'initialiseurs [6]
[modifier | modifier le code]
Pour initialiser un conteneur à l'aide de valeurs connues, il fallait le faire élément par élément. C++11 introduit le patron de classe std::initializer_list
qui permet d'initialiser les conteneurs avec une suite de valeurs entre accolades, autrement dit avec la même syntaxe que celle permettant en C d'initialiser les tableaux.
int sum(const std::initializer_list<int> &list) {
int sum = 0;
for (int i : list) {
sum += i;
}
return sum;
}
sum({1, 2, 3, 4, 5}); // 15
sum({1, 2}) // 3
Les templates
[modifier | modifier le code]Les templates variadiques [7],[8]
[modifier | modifier le code]Pour remplacer les fonctions variadiques du C (déconseillées en C++, car contournant toute vérification du type des paramètres), C++11 introduit les templates variadiques. Ces templates étendent le concept précédent en lui ajoutant la possibilité de prendre un nombre quelconque d'arguments. Elles sont supportées par le compilateur GCC depuis la version 4.3 car elles font partie de l'expérimentation du support de C++0x[9].
Les templates possédant un nombre quelconque d'arguments sont utiles pour implémenter une classe tuple
, qui généralise le concept de paire (triplet, n-uplet, etc.). On illustre les templates variadiques avec cet exemple d'implémentation de la fonction printf
:
void printf(const char *s)
{
while (*s) {
if (*s == '%' && *++s != '%') {
throw std::runtime_error("too few arguments provided to printf");
}
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char* s, const T& value, const Args&... args) {
while (*s) {
if (*s == '%' && *++s != '%') {
std::cout << value;
printf(++s, args...);
return;
}
std::cout << *s++;
}
throw std::runtime_error("extra arguments provided to printf");
}
C++11 définit un certain nombre de concepts que nous pouvons approcher grâce au code source suivant :
template<class ... T> struct Tuple { };
template<class ... T>
void f(T ... args);
template<class ... T> void g(T ... a);
template<class ... T> void h(T ... b)
{
g(b ...);
}
- À la première déclaration, l'argument template
class ... T
est appelé un pack de paramètres template car il regroupe un nombre fini d'arguments (déterminé à la compilation). - À la seconde déclaration,
T ... args
s'appelle un pack de paramètres de fonction. C'est un paramètre de fonction qui englobe un paramètre pour chaque argument contenu par le pack de paramètres templateclass ... T
. - Enfin, la troisième déclaration nous apprend comment utiliser un pack étendu de paramètres. Le mot extension est utilisé car lors de l'appel à g, le pack de paramètres sera étendu avant d'être passé à g.
Les concepts
[modifier | modifier le code]Les concepts (en), initialement prévus pour cette version du C++, ont été repoussés à la norme C++20.
Les chevrons (<>
) [10]
[modifier | modifier le code]Les compilateurs C++03 traitent toujours une séquence de deux signes supérieur à comme un opérateur de décalage binaire vers la droite. En conséquence, lors de l'imbrication de l'utilisation de patrons, les programmeurs sont obligés d'insérer un espace entre les deux chevrons fermants. Par exemple, en C++03, ce code provoque une erreur de compilation :
#include <vector>
std::vector<std::vector<int>> matrix;
// Attention ! Écrire plutôt : “std::vector<std::vector<int> >”
C++11 tente de détecter automatiquement si les symboles doivent jouer le rôle de chevrons fermants ou d'opérateur de décalage binaire.
Template externe [11]
[modifier | modifier le code]Les templates ne sont actuellement pas pris en compte par l'éditeur de liens : il est nécessaire d'incorporer leur définition dans tous les fichiers sources les utilisant en programmation modulaire. Leur compilation était donc longue et gourmande puisque la classe était recompilée dans chaque fichier source, pour chaque type utilisé.
C++11 permet l'utilisation du mot-clé extern
pour rendre les templates globaux. Les fichiers désirant utiliser le template n'ont qu'à le déclarer.
Autres nouvelles fonctionnalités du C++11
[modifier | modifier le code]Assertions statiques [12]
[modifier | modifier le code]La bibliothèque Boost propose déjà cette facilité à travers la macro BOOST_STATIC_ASSERT
. Cependant, son implémentation est étrange, basée sur la métaprogrammation et des comparaisons de taille de structures intermédiaires créées pour l'assertion sans trop de rapport avec le concept.
Par conséquent, intégrer la fonction dans le langage apporte une solution propre au problème.
En pratique, une assertion statique permet de vérifier à la compilation qu'une valeur est vraie. Par exemple, il est possible d'implémenter les concepts en utilisant boost::traits
et BOOST_STATIC_ASSERT
. Si une classe template nécessite que son type template soit un POD (Plain Old Data), elle peut faire une assertion statique sur boost::is_pod<T>::type::value
, ce qui est une constante intégrale de type unspecified-bool-type
et remplit dont le critère pour paraître dans une assertion statique.
En outre, en C++11, l'expression :
static_assert(sizeof(long) > sizeof(int), "La bibliothèque doit être compilée sous un système 64-BIT");
permet à une bibliothèque d'être certaine qu'elle est compilée sur un système où le type long
utilise strictement plus d'octets que le type int
(x86-64 par exemple).
Le mot clé auto
possède une nouvelle sémantique en C++11. Dans les versions précédentes, déclarer une variable automatique indiquait au compilateur qu'elle était valide seulement dans l'espace où elle était déclarée ; comme ce comportement étant aussi celui par défaut, ce mot clé était superflu. En C++11, il change de sémantique et prend la place du type dans la déclaration. Le type sera alors automatiquement décidé par correspondance avec le type retourné par l'objet utilisé pour l'initialisation de la variable. Les variables étant déclarées avec auto
devront donc impérativement être initialisées. Exemple :
auto f = boost::bind(MyFunc, _1);
f(5);
Le type de f
est un type interne de la bibliothèque surchargé environ quatre-vingts fois avec un script Perl[pas clair]. Il était difficile d'écrire explicitement le type pour stocker le résultat d'un bind
dans un objet n'était pas pratique du tout avant le nouveau rôle du mot clé auto
, d'où son apparition. D'une manière générale, l'utilisation du mot clé auto
permet de passer moins de temps à écrire ce que le compilateur sait déjà.
Le nouveau standard ajoute le mot clé decltype
qui permet de typer une variable à partir du type d'une autre variable. Par exemple :
int i;
decltype(i) j = 5;
Le type de j
sera du même type que i
, soit int
. Cette déclaration automatique du type d'une variable peut être très utile dans les templates.
Expressions et fonctions lambda [14],[15],[16]
[modifier | modifier le code]Une fonction lambda construit une fermeture ; c.-à-d. un objet fonction anonyme capable de capturer des variables dans la portée, de prendre des paramètres en entrée et de retourner un résultat. La syntaxe générale et complète est :
[ capture ] ( params ) mutable exception attribute -> ret { body }
. En particulier :
- si aucune variable n'est à capturer,
[ capture ]
devient[]
; - si aucun paramètre n'est à passer, on écrira
( params )
devient()
; - les parties
mutable
,exception
etattribute
sont optionnelles ; - si aucun retour n'est effectué ou si le compilateur peut déterminer le type de retour, on omet la partie
-> ret
.
Voici un exemple qui réalise l'extraction d'un nom de fichier à partir du chemin entier, à l'instar de la commande basename
:
auto basename([] (const std::string &str) { // Spécification du retour explicite '-> const char *' inutile
size_t pos = str.find_last_of("/\\"); // Séparateurs pour Linux et Windows
const char *start = str.c_str();
return pos != std::string::npos ? start + pos + 1 : start;
});
std::cout << "[" << str << "] -> [" << basename(str) << "]\n"; // Utilisation
Sémantique des RValues Reference/Move
[modifier | modifier le code]L'introduction de la sémantique move (déplacement) prend son sens en constatant qu'en C++, il n'y a aucune manière générique de déplacer un objet sans le copier. Par exemple lorsqu'une fonction renvoie un objet de grosse taille, celui-ci est copié dans une zone temporaire avant d'être à nouveau copié là où le résultat de la fonction est affecté. Après chaque étape de copie l'objet copié devient inutile et est détruit. Il serait beaucoup plus efficace de déplacer l'objet plutôt que de le recopier et détruire l'original. C'est particulièrement vrai si l'objet est d'un type proche du type T
ci-dessous, où LargeDataT
est un type d'objet coûteux à dupliquer :
class T
{
LargeDataT *ptr;
public:
T(const T &x) : ptr ( new LargeDataT (*x.ptr) ) {}
~T() { delete ptr; }
void MoveFrom (T& x) { ptr = x.ptr; x.ptr = nullptr; }
};
En effet le déplacement d'un objet de ce type T
requiert simplement la recopie du membre ptr
alors que sa duplication alloue et copie un nouvel objet LargeDataT
. Le problème que résout C++11 par l'ajout des RValues reference est de pouvoir appeler la fonction MoveFrom
en lieu et place du constructeur de recopie dans les cas où la copie correspond à un déplacement.
Ceci s'obtient par l'ajout du constructeur de déplacement ci-dessous :
T(T &&x) : ptr (x.ptr) { x.ptr = nullptr; }
Le double & marque la référence sur rvalue (parfois aussi appelée temporaire). C'est-à-dire une référence sur quelque chose qui est temporaire ou est sur le point d'être détruit. Le constructeur de déplacement sera donc choisi par le compilateur à la place du constructeur de recopie en cas de copie d'un objet temporaire ou sur le point d'être supprimé. Sur tous les autres aspects une référence sur une rvalue est identique à une référence classique maintenant appelée référence sur lvalue (que l'on peut définir grossièrement par : tout ce qui a une adresse).
De cette définition ressort un fait qui peut sembler paradoxal : une variable de type référence sur une rvalue n'est généralement pas une référence sur une rvalue ! En effet à partir du moment où une référence sur une rvalue est écrite dans une variable, y compris si elle est de type référence sur rvalue, elle perd son caractère temporaire dans l'espace de définition de cette variable.
Mais parfois il est utile d'appeler le constructeur de déplacement même à partir d'une variable qui n'est pas temporaire. Par exemple la commande swap
est souvent introduite par le patron de fonction ci-dessous :
template <class T>
void swap ( T& a, T& b )
{
T c(a);
a=b;
b=c;
}
Cette fonction a pour inconvénient d'appeler d'abord le constructeur de recopie, puis deux opérateurs d'assignation. Ce sont donc 3 copies au total, qui sont des opérations coûteuses si les objets impliqués sont de taille importante. Ici le constructeur de déplacement n'est pas appelé car a, b et c comme source de la copie ne sont pas temporaires.
C++11 introduit la fonction std::move()
qui renvoie une référence à une rvalue et prend pour paramètre une référence à une lvalue ou à une rvalue. Son patron est le suivant :
template <class T>
typename remove_reference<T>::type&&
move(T&& a)
{
return a;
}
La fonction move()
donne à ce qu'il retourne la valeur de son paramètre. La fonction move ne modifie pas l'objet qui lui est passé mais reçoit et fournit une référence sur un objet non constant. L'objet d'origine peut donc être modifié à partir du résultat de la fonction move()
. Le point important de move est qu'il n'y a aucune copie de faite. En utilisant move, on peut ainsi réécrire de façon concise swap()
, sans qu'il n'y ait de copie.
template <class T>
void swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
Un autre intérêt de move()
est de permettre d'obtenir une copie d'un objet volumineux sans qu'il y ait de copie réelle de celui-ci, en particulier de la partie volumineuse. De plus, move()
est impératif[Quoi ?] dans le cas d'un objet non copiable comme le “smart pointer” unique_ptr
.
Énumérations fortement typées [17]
[modifier | modifier le code]L'énumération du langage C est similaire à une liste de définitions de symboles (macros) correspondant à des nombres entiers, et les versions de C++ antérieures à C++11 n'avaient répondu qu'en interdisant la conversion d'un type énumération dans un autre.
C++11 proposera des énumérations « fortement typées ». Ces énumérations seront obtenues en remplaçant enum
par enum class
ou enum struct
.
La conversion implicite d'éléments de ces énumérations vers les entiers sera prohibée et l'accès aux éléments se fera à l'aide de l'opérateur de résolution de portée. Voici un exemple d'utilisation :
enum class Chiffres { Zero, Un, Deux, Trois, Quatre, Cinq, Six, Sept, Huit, Neuf };
Chiffres chif;
chif = Chiffres::Sept;
int nb;
nb = (int) Chiffres::Trois;
L'opérateur de résolution de portée est optionnel avec des énumérations faiblement typées :
enum Enum2 { E_1, E_2 };
Enum2 e1 = E_1; // Comme en C++03
Enum2 e2 = Enum2::E_2; // Comme avec une enum class;
De plus, C++11 vous permettra de choisir le type d'entier sous-jacent des énumérations (tous sauf wchar_t
):
enum class Enumeration : unsigned short { Valeur1, Valeur2 };
Par défaut, ce type sera int
.
Ce comportement sera aussi possible avec les énumérations normalement typées, et il sera bien sûr toujours possible de définir la valeur d'une partie de l'énumération :
enum EnumerationNormale : UINT8 { ValeurDepart = 0, ValeurMoyenne = 127, ValeurMaximum = 255 };
Boucles basées sur des intervalles [18]
[modifier | modifier le code]Le code nécessaire en C++ pour le parcours d'un intervalle et l'action sur ses éléments était lourde et longue. De nombreux langages, comme Java, ont fourni à leurs utilisateurs un opérateur foreach
qui permet de parcourir une liste avec aisance[note 1]. Pour répondre aux attentes, la norme C++11 fournit la syntaxe de l'instruction for
qui s'implémentera de cette façon :
int mon_tableau[5] = {1, 2, 3, 4, 5};
for (int &x: mon_tableau) {
x *= 2;
}
Le code précédent double chaque élément du tableau mon_tableau
. L'entier x
défini pour le corps de la boucle for
référence successivement chacun des éléments du tableau. Ce type de parcours fonctionnera pour les listes classiques, les listes d'initialiseurs, ainsi que les conteneurs de la STL définissant les fonctions membres begin
et end
comme dans l'exemple suivant :
std::vector<int> myvector;
myvector.push_back(100);
myvector.push_back(200);
myvector.push_back(300);
// Show content
std::cout << "\nShow content of " << myvector.size() << " elements\n";
std::cout << "Version with iterator\n";
for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
std::cout << *it << '\n';
}
std::cout << "Version with [] operator\n";
for (size_t n = 0; n < myvector.size(); ++n) {
std::cout << myvector[n] << '\n';
}
std::cout << "Version 'foreach'\n";
for (int value: myvector) { // for (auto value: myvector) est recommandé car aucune ambiguïté
std::cout << value << '\n';
}
Littéraux définis par l'utilisateur [19]
[modifier | modifier le code]
Les littéraux définis par l'utilisateur[note 2] permettent d'ajouter de nouveaux suffixes, au même titre que UL
, ULL
, L
... Les suffixes créés ainsi doivent commencer par un tiret bas (_
) afin de ne pas entrer en conflit avec les futurs littéraux introduits dans la librairie standard. Par exemple :
struct Distance
{
int d; // d in mm
Distance(int mm):
d(mm);
{}
}
inline constexpr Distance operator ""_mm(unsigned long long value)
{
return Distance(value);
}
inline constexpr Distance operator ""_m(unsigned long long value)
{
return Distance(value * 1000);
}
inline constexpr Distance operator ""_cm(unsigned long long value)
{
return Distance(value * 10);
}
auto DistanceChaiseBureau = 20_cm; // Instance de Distance, d = 200
auto DistanceMachineACafe = 5_m; // Instance de Distance, d = 5000
Pointeur NULL
[20]
[modifier | modifier le code]Le nouveau mot-clé nullptr
est une constante du langage avec le caractère particulier d'être assignable à tous les types de pointeurs. En effet, contrairement au C où la macro préprocesseur est généralement définie avec #define NULL ((void*)0)
, en C++ il est interdit d'assigner un void*
à un pointeur d'un type différent. L'usage était donc de définir NULL
avec l'entier 0
. Ce comportement restera compatible, mais il sera aussi possible d'écrire :
T* ptr = nullptr;
La constante NULL
définie comme l'entier 0
ne permettait pas au compilateur de déterminer quelle surcharge de f
choisir dans le code suivant :
void f(int);
void f(void*);
f(0); // Entier 0 ou pointeur nul?
Le mot clé nullptr
est une constante du type nullptr_t
, non convertible en entier. Pour appeler la fonction f
avec un pointeur NULL
, la surcharge est correctement choisie en C++11 dans le code suivant :
void f(int);
void f(void*);
f(0); // Entier 0, pas d'ambiguïté
f(nullptr); // Convertible en void*, mais pas en int.
Extension de la bibliothèque standard
[modifier | modifier le code]Threads
[modifier | modifier le code]En C++11, la bibliothèque standard implemente le modèle de classe std::thread
, en s'inspirant de l'implémentation des threads de la bibliothèque Boost. Voici, un exemple illustrant son utilisation :
#include <thread>
void tacheGeniale(std::string msg) { std::cout << "tâche géniale dit : " << msg; }
int main ()
{
/* Création et lancement du thread t qui exécute la fonction tacheGeniale
avec "bonjour" comme paramètre */
std::thread t(tacheGeniale, "bonjour");
t.join(); // Attend la fin du thread t
}
Type tuple
[modifier | modifier le code]Un tuple est une collection de dimension fixe d'objets de types potentiellement différents. Tout type d'objet peut être élément d'un tuple. Cette nouvelle fonctionnalité est implémentée dans un nouvel en-tête et bénéficie des extensions de C++11 comme :
- Les templates variadiques
- Référence sur référence
- Arguments par défaut pour les fonctions template
Le patron de classe tuple
est déclaré par la ligne :
template <class... Types> class tuple;
Un exemple de définition et d'utilisation du type tuple
:
typedef tuple< int, double, long &, const char * > test_tuple ;
long lengthy = 12 ;
test_tuple proof( 18, 6.5, lengthy, "Ciao!" ) ;
lengthy = get<0>(proof) ; // Assigne à ‘lengthy’ la valeur 18
get<3>(proof) = " Beautiful!" ; // Modifie la {{4e}} valeur du tuple
Il est possible de créer le tuple proof
sans définir son contenu si les éléments du tuple possèdent un constructeur par défaut. De plus, il est possible d'assigner un tuple à un autre tuple : si les deux tuples sont de même type, il est nécessaire que chaque élément du tuple ait un constructeur par copie, sinon il faut que le type de chaque élément de l'opérande de droite soit compatible avec le type correspondant dans l'opérande de gauche ou que l'élément correspondant de l'opérande gauche ait un constructeur approprié.
typedef tuple< int , double, string > tuple_1 t1 ;
typedef tuple< char, short , const char * > tuple_2 t2( 'X', 2, "Hola!" ) ;
t1 = t2 ; // OK : les deux premiers éléments peuvent être convertis,
// le troisième peut être construit à partir du ‘const char *’.
Les opérateurs relationnels sont disponibles (pour les tuples ayant le même nombre d'éléments). Deux expressions sont introduites pour vérifier les caractéristiques d'un tuple (à la compilation) :
tuple_size<T>::value<
retourne le nombre d'éléments du tupleT
,tuple_element<I, T>::type
retourne le type de l'objet placé en positionI
du tupleT
.
Table de hachage
[modifier | modifier le code]Intégrer les tables de hachage (conteneurs associatifs non ordonnés) dans la bibliothèque standard du C++ était une demande récurrente. Cela n'avait pas été réalisé pour la norme écrite en 1995 et approuvée en 1998 à cause des contraintes de temps. Bien que cette solution soit moins efficace que les arbres équilibrés dans le pire des cas (en cas de collisions importantes), elle est cependant la meilleure dans la plupart des applications réelles.
Les collisions seront uniquement gérées par chaînage linéaire car le comité ne considère pas opportun de standardiser des solutions d'adressage ouvert qui introduisent un nombre important de problèmes intrinsèques[réf. nécessaire] (en particulier quand la suppression d'éléments est permise).
Pour éviter les conflits de noms avec les bibliothèques non standards qui ont leur propre implémentation des tables de hachage, on utilisera le préfixe unordered
, au lieu de hash
.
Cette nouvelle fonctionnalité intégrera quatre types de tables de hachage, différentes selon qu'elles acceptent ou non des éléments de même clé (clé unique ou clé équivalente) et qu'elles associent chaque clé à la valeur associée.
Type de table de hachage | Type associé arbitraire | Clés équivalentes |
---|---|---|
unordered_set | ||
unordered_multiset | • | |
unordered_map | • | |
unordered_multimap | • | • |
Ces nouvelles classes remplissent toutes les demandes des classes de conteneurs et contiennent toutes les méthodes nécessaires pour accéder aux éléments : insert
, erase
, begin
, end
.
Ces classes n'ont pas nécessité les nouvelles extensions de C++11 mais seulement une légère extension du header <functional>
et l'introduction des headers <unordered_set>
et <unordered_map>
.
Aucun autre changement aux classes n'est nécessaire et elles ne dépendent d'aucune autre extension de la bibliothèque standard.
Expressions rationnelles
[modifier | modifier le code]La bibliothèque définie dans le fichier d'en-tête <regex>
est constitué d'un ensemble de nouvelles classes :
- Les expressions rationnelles sont représentées par une instance de la classe template
std::regex
- Les résultats sont représentés par une instance de la classe template
std::match_results
La fonction std::regex_search
est utilisée pour une recherche.
std::regex_replace
est utilisée pour effectuer un "chercher-remplacer", elle renvoie pour cela une nouvelle chaîne.Les algorithmes std::regex_search
et std::regex_replace
prennent une expression rationnelle et une chaîne et écrivent les occurrences trouvées dans la structure std::match_results
.
Voici un exemple d'utilisation de std::match_results
:
const char *reg_esp = "[ ,.\\t\\n;:]"; // Une liste de caractères séparateurs
// On pourrait aussi utiliser les chaînes littérales
// const char *reg_esp = R"([ ,.\t\n;:])";
std::regex rgx(reg_esp); // 'regex' est une instance de la classe
// 'basic_regex' avec un argument de type 'char'.
std::cmatch match; // 'cmatch' est une instance de la classe
// 'match_results' avec un argument de type 'const char *'.
const char *target = "Unseen University - Ankh-Morpork";
// Trouve tous les mots de 'target' séparés par les caractères de 'reg_esp'.
if( std::regex_search( target, match, rgx ) ) {
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 << "\n";
}
}
Notez l'utilisation du double backslash, car le C++ utilise le backslash comme un caractère d'échappement. Les chaînes littérales en C++11 peuvent permettre d'éviter le problème. L'utilisation de la bibliothèque <regex>
ne requiert aucune dépendance explicite.
Amélioration des nombres aléatoires extensibles
[modifier | modifier le code]La bibliothèque standard du C permet de générer des nombres pseudo-aléatoires grâce à la fonction rand
. L'algorithme de génération n'est pas standardisé mais laissé au choix du fournisseur de la bibliothèque. Le C++ n'y a rien changé, mais C++11 va fournir une manière différente de générer les nombres pseudo-aléatoires. Cette fonctionnalité est découpée en deux parties qui forment un objet de génération de nombres aléatoires :
- un moteur de génération, qui contient l'état du générateur et produit les nombres pseudo-aléatoires ;
- une distribution, qui détermine les valeurs que le résultat peut prendre ainsi que sa loi de probabilité.
C++11 définit trois algorithmes de génération, chacun ayant des avantages et des inconvénients.
Template | Entier/flottant | Qualité | Vitesse | Taille d'état |
---|---|---|---|---|
linear_congruential
|
Entier | Moyenne | Moyenne | 1 |
subtract with carry
|
Les deux | Moyenne | Rapide | 25 |
mersenne_twister
|
Entier | Bonne | Rapide | 624 |
C++11 fournira un certain nombre de lois standard : uniform_int_distribution
, bernoulli_distribution
, geometric_distribution
, poisson_distribution
, binomial_distribution
, uniform_real_distribution
, exponential_distribution
, normal_distribution
et gamma_distribution
.
Le générateur et la distribution se combinent comme dans l'exemple suivant :
std::uniform_int_distribution<int> distribution(0, 99);
std::mt19937 engine;
auto generator = std::bind(distribution, engine);
int random = generator(); // Generate a uniform integral variate between 0 and 99.
Fonctions mathématiques spéciales
[modifier | modifier le code]Le fichier header <math>
définissait déjà plusieurs fonctions mathématiques usuelles :
- trigonométriques :
sin
,cos
,tan
,asin
,acos
,atan
,atan2
; - hyperboliques :
sinh
,cosh
,tanh
,asinh
,acosh
,atanh
; - exponentielles :
exp
,exp2
,frexp
,ldexp
,expm1
; - logarithmiques :
log10
,log2
,logb
,ilogb
,log1p
; - puissances :
pow
,sqrt
,cbrt
,hypot
; - spéciales :
erf
,erfc
,tgamma
,lgamma
.
Pour C++11, le comité a décidé d'ajouter de nouvelles fonctions qui nécessitaient auparavant l'utilisation de bibliothèques non-standards. Ces nouvelles fonctions ont un intérêt principalement pour les programmeurs de disciplines scientifiques et pour l'ingénierie. Le tableau suivant montre les 23 fonctions décrites dans TR1[réf. nécessaire].
Nom de la fonction | Prototype de la fonction | Expression mathématique |
---|---|---|
Polynômes de Laguerre généralisés | double assoc_laguerre(unsigned n, unsigned m, double x); |
|
Polynômes de Legendre généralisés | double assoc_legendre(unsigned l, unsigned m, double x); |
|
Fonction bêta | double beta(double x, double y); |
|
Intégrale elliptique complète de premier genre | double comp_ellint_1(double k); |
|
Intégrale elliptique complète de deuxième genre | double comp_ellint_2(double k); |
|
Intégrale elliptique complète de troisième genre | double comp_ellint_3(double k, double nu); |
|
Fonctions hypergéométriques confluentes | double conf_hyperg(double a, double c, double x); |
|
Fonctions de Bessel cylindriques modifiées régulières | double cyl_bessel_i(double nu, double x); |
|
Fonctions de Bessel cylindriques du premier genre | double cyl_bessel_j(double nu, double x); |
|
Fonctions de Bessel cylindriques modifiées irrégulières | double cyl_bessel_k(double nu, double x); |
|
Fonctions de Neumann cylindriques | double cyl_neumann(double nu, double x); |
|
Intégrale elliptique incomplète du premier genre | double ellint_1(double k, double phi); |
|
Intégrale elliptique incomplète du deuxième genre | double ellint_2(double k, double phi); |
|
Intégrale elliptique incomplète du troisième genre | double ellint_3(double k, double nu, double phi); |
|
Intégrale exponentielle | double expint(double x); |
|
Polynômes d'Hermite | double hermite(unsigned n, double x); |
|
Séries hypergéométriques | double hyperg(double a, double b, double c, double x); |
|
Polynômes de Laguerre | double laguerre(unsigned n, double x); |
|
Polynômes de Legendre | double legendre(unsigned l, double x); |
|
Fonction zêta de Riemann | double riemann_zeta(double x); |
|
Fonctions sphériques de Bessel du premier genre | double sph_bessel(unsigned n, double x); |
|
Fonctions sphériques de Legendre généralisées | double sph_legendre(unsigned l, unsigned m, double theta); |
|
Fonctions sphériques de Neumann | double sph_neumann(unsigned n, double x); |
Chacune de ces fonctions possède deux variantes supplémentaires. En rajoutant le suffixe f
ou l
au nom de la fonction, on obtient les mêmes fonctions agissant sur des float
ou des long double
respectivement. Par exemple :
float sph_neumannf( unsigned n, float x ) ;
long double sph_neumannl( unsigned n, long double x ) ;
Notes et références
[modifier | modifier le code]Notes
[modifier | modifier le code]- L'algorithme
std::for_each
du C++ ne sert qu'à appliquer une fonction à tous les éléments d'une séquence de conteneur (voir la documentation de cette fonction). - Explication détaillée et exemples en 3 parties : (en) « Partie 1 » - (en) « Partie 2 » - (en) « Partie 3 ».
Références
[modifier | modifier le code]- Blog d'Herb Sutter : March 2010 ISO C++ Standards Meeting.
- Blog d'Herb Sutter : We have an international standard: C++0x is unanimously approved.
- (en) « ISO/CEI 14882:2011 », ISO.
- (en) Herb Sutter et Francis Glassborow, « N1986 - Delegating Constructors » [PDF], 2006‐04‐06.
- (en) Alisdair Meredith, Michael Wong et Jens Maurer, « N2540 - Inheriting Constructors », .
- (en) Jason Merrill et Daveed Vandevoorde, « N2672 - Initializer List proposed wording », .
- (en) Douglas Gregor, Jaakko Järvi, Jens Maurer et Jason Merrill, « N2242 - Proposed Wording for Variadic Templates » [PDF], .
- (en) Douglas Gregor et Eric Niebler, « N2555 - Extending Variadic Template Template Parameters » [PDF], .
- (en) « Status of Experimental C++0x Support in GCC 4.3 – GNU Project – Free Software Foundation (FSF) ».
- (en) Daveed Vandevoorde, « N1757 - Right Angle Brackets », .
- (en) John Spicer, « N1987 - Adding "extern template" », .
- (en) Robert Klarer, Dr. John Maddock, Beman Dawes et Howard Hinnant, « N1720 - Proposal to Add Static Assertions to the Core Language », .
- (en) Jaakko Järvi, Bjarne Stroustrup et Gabriel Dos Reis, « N1984 - Deducing the type of variable from its initializer expression » [PDF], .
- (en) Jaakko Järvi, John Freeman et Lawrence Crowl, « N2550 - Lambda Expressions and Closures: Wording for Monomorphic Lambdas » [PDF], .
- (en) Jaakko Järvi, Peter Dimov et John Freeman, « N2658 - Constness of Lambda Functions » [PDF], .
- (en) Daveed Vandevoorde, « N2927 - New wording for C++0x Lambdas » [PDF], .
- (en) Herb Sutter et David E. Miller, « N1719 - Strongly Typed Enums » [PDF], .
- (en) Douglas Gregor et Beman Dawes, « N2930 - Range-Based For Loop Wording », .
- (en) Ian McIntosh, Michael Wong, Raymond Mak et al., « N2765 - User-defined Literals » [PDF], .
- (en) Herb Sutter et Bjarne Stroustrup, « N2431 - A name for the null pointer: nullptr » [PDF], .