C++11 - C++11

C++11 est une version de la norme ISO / IEC 14882 pour le langage de programmation C++ . C++11 a remplacé la version précédente du standard C++, appelée C++03 , et a ensuite été remplacée par C++14 . Le nom suit la tradition de nommer les versions linguistiques par l'année de publication de la spécification, bien qu'il s'appelait auparavant C++0x car il devait être publié avant 2010.

Bien que l'un des objectifs de conception était de préférer les modifications apportées aux bibliothèques aux modifications apportées au langage de base , C++11 apporte plusieurs ajouts au langage de base. Les domaines du langage de base qui ont été considérablement améliorés incluent la prise en charge du multithreading, la prise en charge de la programmation générique , l'initialisation uniforme et les performances. Des changements importants ont également été apportées à la bibliothèque standard C ++ , intégrant la plupart des rapports techniques C ++ 1 (TR1) bibliothèques , à l' exception de la bibliothèque de fonctions mathématiques spéciales.

C++11 a été publié en tant qu'ISO/IEC 14882:2011 en septembre 2011 et est disponible moyennant des frais. L'ébauche de travail la plus similaire à la norme C++11 publiée est N3337, datée du 16 janvier 2012 ; il n'a que des corrections éditoriales du standard C++11.

Objectifs de conception

Le comité de conception a tenté de s'en tenir à un certain nombre d'objectifs lors de la conception de C++11 :

  • Maintenir la stabilité et la compatibilité avec C++98 et éventuellement avec C
  • Préférez introduire de nouvelles fonctionnalités via la bibliothèque standard, plutôt que d'étendre le langage de base
  • Préférez les changements qui peuvent faire évoluer la technique de programmation
  • Améliorer C++ pour faciliter la conception de systèmes et de bibliothèques, plutôt que d'introduire de nouvelles fonctionnalités utiles uniquement à des applications spécifiques
  • Augmenter la sécurité de type en fournissant des alternatives plus sûres aux techniques dangereuses antérieures
  • Augmenter les performances et la capacité de travailler directement avec le matériel
  • Fournir des solutions appropriées aux problèmes du monde réel
  • Implémentez le principe de zéro surcoût (le support supplémentaire requis par certains utilitaires ne doit être utilisé que si l'utilitaire est utilisé)
  • Rendre le C++ facile à enseigner et à apprendre sans supprimer aucun utilitaire nécessaire aux programmeurs experts

L'attention portée aux débutants est considérée comme importante, car la plupart des programmeurs informatiques le seront toujours, et parce que de nombreux débutants n'élargissent jamais leurs connaissances, se limitant à travailler dans des aspects du langage dans lequel ils se spécialisent.

Extensions du langage de base C++

L'une des fonctions du comité C++ est le développement du noyau du langage. Les domaines du langage de base qui ont été considérablement améliorés incluent la prise en charge du multithreading, la prise en charge de la programmation générique , l'initialisation uniforme et les performances.

Améliorations des performances du core language runtime

Ces fonctionnalités de langage existent principalement pour fournir une sorte d'avantage en termes de performances, soit en termes de mémoire, soit en termes de vitesse de calcul.

Références Rvalue et constructeurs de déplacement

En C++03 (et avant), les temporaires (appelés " rvalues ", car ils se trouvent souvent du côté droit d'une affectation) étaient destinés à ne jamais être modifiables — tout comme en C — et étaient considérés comme impossibles à distinguer des const T&types ; néanmoins, dans certains cas, les temporaires auraient pu être modifiés, un comportement qui a même été considéré comme une échappatoire utile. C++11 ajoute un nouveau type de référence non-const appelé unréférence rvalue , identifiée parT&&. Cela fait référence aux temporaires qui peuvent être modifiés après leur initialisation, dans le but de permettre la "sémantique de déplacement".

Un problème de performances chronique avec C++03 est les copies profondes coûteuses et inutiles qui peuvent se produire implicitement lorsque les objets sont passés par valeur. Pour illustrer le problème, considérons qu'un std::vector<T>est, en interne, un wrapper autour d'un tableau de style C avec une taille définie. Si un std::vector<T>temporaire est créé ou renvoyé à partir d'une fonction, il ne peut être stocké qu'en créant un nouveau std::vector<T>et en y copiant toutes les données de rvalue. Ensuite, le temporaire et toute sa mémoire sont détruits. (Pour plus de simplicité, cette discussion néglige l' optimisation de la valeur de retour .)

En C++11, un move constructeur destd::vector<T>qui prend une référence rvalue à unstd::vector<T>peut copier le pointeur vers le tableau interne de style C hors de la rvalue dans le nouveaustd::vector<T>, puis définissez le pointeur à l'intérieur de la rvalue sur null. Étant donné que le temporaire ne sera plus jamais utilisé, aucun code n'essaiera d'accéder au pointeur null, et comme le pointeur est null, sa mémoire n'est pas supprimée lorsqu'il sort de la portée. Par conséquent, l'opération renonce non seulement aux dépenses d'une copie en profondeur, mais est sûre et invisible.

Les références Rvalue peuvent offrir des avantages en termes de performances au code existant sans qu'il soit nécessaire d'apporter des modifications en dehors de la bibliothèque standard. Le type de la valeur renvoyée d'une fonction renvoyant un std::vector<T>temporaire n'a pas besoin d'être modifié explicitement std::vector<T> &&pour appeler le constructeur de déplacement, car les temporaires sont automatiquement considérés comme des rvalues. (Cependant, s'il std::vector<T>s'agit d'une version C++03 sans constructeur de déplacement, le constructeur de copie sera invoqué avec un const std::vector<T>&, entraînant une allocation de mémoire importante.)

Pour des raisons de sécurité, certaines restrictions sont imposées. Une variable nommée ne sera jamais considérée comme une rvalue même si elle est déclarée comme telle. Pour obtenir une rvalue, le modèle de fonction std::move()doit être utilisé. Les références Rvalue peuvent également être modifiées uniquement dans certaines circonstances, étant destinées à être utilisées principalement avec les constructeurs de déplacement.

En raison de la nature de la formulation des références rvalue et de certaines modifications apportées à la formulation des références lvalue (références régulières), les références rvalue permettent aux développeurs de fournir une transmission de fonction parfaite. Lorsqu'elle est combinée avec des modèles variadiques , cette capacité permet des modèles de fonction qui peuvent parfaitement transmettre des arguments à une autre fonction qui prend ces arguments particuliers. Ceci est très utile pour transmettre les paramètres du constructeur, pour créer des fonctions d'usine qui appelleront automatiquement le constructeur correct pour ces arguments particuliers. Cela se voit dans l' ensemble emplace_back des méthodes de la bibliothèque standard C++.

constexpr – Expressions constantes généralisées

C++ a toujours eu le concept d'expressions constantes. Ce sont des expressions telles que celles 3+4qui produiront toujours les mêmes résultats, au moment de la compilation et au moment de l'exécution. Les expressions constantes sont des opportunités d'optimisation pour les compilateurs, et les compilateurs les exécutent fréquemment au moment de la compilation et codent en dur les résultats dans le programme. De plus, à plusieurs endroits, la spécification C++ requiert l'utilisation d'expressions constantes. La définition d'un tableau nécessite une expression constante et les valeurs d'énumérateur doivent être des expressions constantes.

Cependant, une expression constante n'a jamais été autorisée à contenir un appel de fonction ou un constructeur d'objet. Donc un morceau de code aussi simple que celui-ci est invalide :

int get_five() {return 5;}

int some_value[get_five() + 7]; // Create an array of 12 integers. Ill-formed C++

Ce n'était pas valide en C++03, car ce get_five() + 7n'est pas une expression constante. Un compilateur C++03 n'a aucun moyen de savoir s'il get_five()est réellement constant à l'exécution. En théorie, cette fonction pourrait affecter une variable globale, appeler d'autres fonctions constantes non exécutables, etc.

C++11 a introduit le mot-clé constexpr, qui permet à l'utilisateur de garantir qu'un constructeur de fonction ou d'objet est une constante de compilation. L'exemple ci-dessus peut être réécrit comme suit :

constexpr int get_five() {return 5;}

int some_value[get_five() + 7]; // Create an array of 12 integers. Valid C++11

Cela permet au compilateur de comprendre et de vérifier qu'il get_five()s'agit d'une constante de compilation.

L'utilisation constexprsur une fonction impose certaines limites à ce que cette fonction peut faire. Premièrement, la fonction doit avoir un type de retour non void. Deuxièmement, le corps de la fonction ne peut pas déclarer de variables ou définir de nouveaux types. Troisièmement, le corps ne peut contenir que des déclarations, des instructions null et une seule instruction return. Il doit exister des valeurs d'argument telles que, après la substitution d'argument, l'expression dans l'instruction return produise une expression constante.

Avant C++11, les valeurs des variables ne pouvaient être utilisées dans des expressions constantes que si les variables sont déclarées const, ont un initialiseur qui est une expression constante et sont de type intégral ou énumération. C++11 supprime la restriction selon laquelle les variables doivent être de type intégral ou énumération si elles sont définies avec le constexprmot - clé :

constexpr double earth_gravitational_acceleration = 9.8;
constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;

De telles variables de données sont implicitement const et doivent avoir un initialiseur qui doit être une expression constante.

Pour construire des valeurs de données d'expression constante à partir de types définis par l'utilisateur, les constructeurs peuvent également être déclarés avec constexpr. constexprLe corps de la fonction d' un constructeur ne peut contenir que des déclarations et des instructions null, et ne peut pas déclarer de variables ou définir des types, comme avec une constexprfonction. Il doit exister des valeurs d'argument telles que, après la substitution d'argument, il initialise les membres de la classe avec des expressions constantes. Les destructeurs de ces types doivent être triviaux.

Le constructeur de copie d'un type avec n'importe quel constexprconstructeur doit généralement être également défini en tant que constexprconstructeur, pour permettre aux objets du type d'être renvoyés par valeur à partir d'une fonction constexpr. Toute fonction membre d'une classe, telle que les constructeurs de copie, les surcharges d'opérateur, etc., peut être déclarée en tant que constexpr, tant qu'elle répond aux exigences des fonctions constexpr. Cela permet au compilateur de copier des objets au moment de la compilation, d'effectuer des opérations sur eux, etc.

Si une fonction ou un constructeur constexpr est appelé avec des arguments qui ne sont pas des expressions constantes, l'appel se comporte comme si la fonction n'était pas constexpr et la valeur résultante n'est pas une expression constante. De même, si l'expression dans l'instruction return d'une fonction constexpr n'est pas évaluée à une expression constante pour une invocation donnée, le résultat n'est pas une expression constante.

constexprdiffère de consteval, introduit en C++20 , en ce que ce dernier doit toujours produire une constante de temps de compilation, tandis que constexprn'a pas cette restriction.

Modification de la définition des anciennes données simples

En C++03, une classe ou une structure doit suivre un certain nombre de règles pour être considérée comme un type POD ( plain old data ). Les types qui correspondent à cette définition produisent des mises en page d'objets compatibles avec C, et ils peuvent également être initialisés de manière statique. Le standard C++03 a des restrictions sur les types qui sont compatibles avec C ou qui peuvent être initialisés statiquement malgré qu'il n'y ait aucune raison technique pour laquelle un compilateur ne pourrait pas accepter le programme ; si quelqu'un créait un type POD C++03 et ajoutait une fonction membre non virtuelle, ce type ne serait plus un type POD, ne pourrait pas être initialisé de manière statique et serait incompatible avec C malgré aucun changement dans la disposition de la mémoire .

C++11 a assoupli plusieurs des règles POD, en divisant le concept POD en deux concepts distincts : trivial et standard-layout .

Un type trivial peut être initialisé de manière statique. Cela signifie également qu'il est valide de copier des données autour de via memcpy, plutôt que d'avoir à utiliser un constructeur de copie. La durée de vie d'un type trivial commence lorsque son stockage est défini, pas lorsqu'un constructeur se termine.

Une classe ou une structure triviale est définie comme celle qui :

  1. Possède un constructeur par défaut trivial. Cela peut utiliser la syntaxe du constructeur par défaut ( SomeConstructor() = default;).
  2. Possède des constructeurs de copie et de déplacement triviaux, qui peuvent utiliser la syntaxe par défaut.
  3. Possède des opérateurs d'affectation de copie et de déplacement triviaux, qui peuvent utiliser la syntaxe par défaut.
  4. Possède un destructeur trivial, qui ne doit pas être virtuel.

Les constructeurs ne sont triviaux que s'il n'y a pas de fonctions membres virtuelles de la classe et pas de classes de base virtuelles. Les opérations de copie/déplacement nécessitent également que tous les membres de données non statiques soient triviaux.

Un type de mise en page standard signifie qu'il commande et emballe ses membres d'une manière compatible avec C. Une classe ou une structure est de mise en page standard, par définition, à condition :

  1. Il n'a pas de fonctions virtuelles
  2. Il n'a pas de classes de base virtuelles
  3. Tous ses membres de données non statiques ont le même contrôle d'accès (public, privé, protégé)
  4. Tous ses membres de données non statiques, y compris ceux de ses classes de base, sont dans la même classe de la hiérarchie
  5. Les règles ci-dessus s'appliquent également à toutes les classes de base et à tous les membres de données non statiques dans la hiérarchie des classes
  6. Il n'a pas de classes de base du même type que le premier membre de données non statique défini

Une classe/struct/union est considérée comme un POD s'il est trivial, standard et que tous ses membres de données et classes de base non statiques sont des POD.

En séparant ces concepts, il devient possible d'abandonner l'un sans perdre l'autre. Une classe avec des constructeurs de déplacement et de copie complexes peut ne pas être triviale, mais elle pourrait être de mise en page standard et ainsi interagir avec C. De même, une classe avec des membres de données non statiques publics et privés ne serait pas de mise en page standard, mais elle pourrait l'être. trivial et donc memcpy-capable.

Améliorations des performances au moment de la création du langage de base

Modèle externe

En C++03, le compilateur doit instancier un modèle chaque fois qu'un modèle entièrement spécifié est rencontré dans une unité de traduction. Si le modèle est instancié avec les mêmes types dans de nombreuses unités de traduction, cela peut augmenter considérablement les temps de compilation. Il n'y a aucun moyen d'empêcher cela dans C++03, donc C++11 a introduit des déclarations de modèles externes, analogues aux déclarations de données externes.

C++03 a cette syntaxe pour obliger le compilateur à instancier un modèle :

template class std::vector<MyClass>;

C++11 fournit maintenant cette syntaxe :

extern template class std::vector<MyClass>;

qui indique au compilateur de ne pas instancier le modèle dans cette unité de traduction.

Améliorations de la convivialité du langage de base

Ces fonctionnalités existent dans le but principal de rendre la langue plus facile à utiliser. Ceux-ci peuvent améliorer la sécurité du type, minimiser la répétition du code, rendre le code erroné moins probable, etc.

Listes d'initialisation

C++03 a hérité de la fonctionnalité initializer-list de C. Une structure ou un tableau reçoit une liste d'arguments entre accolades, dans l'ordre des définitions des membres dans la structure. Ces listes d'initialisation sont récursives, donc un tableau de structures ou de structures contenant d'autres structures peut les utiliser.

struct Object
{
    float first;
    int second;
};

Object scalar = {0.43f, 10}; //One Object, with first=0.43f and second=10
Object anArray[] = {{13.4f, 3}, {43.28f, 29}, {5.934f, 17}}; //An array of three Objects

C'est très utile pour les listes statiques, ou pour initialiser une structure à une certaine valeur. C++ fournit également des constructeurs pour initialiser un objet, mais ils ne sont souvent pas aussi pratiques que la liste d'initialisation. Cependant, C++03 autorise les listes d'initialisation uniquement sur les structures et les classes conformes à la définition POD (Plain Old Data) ; C++11 étend les listes d'initialisation, afin qu'elles puissent être utilisées pour toutes les classes, y compris les conteneurs standard comme std::vector.

C++11 lie le concept à un modèle, appelé std::initializer_list. Cela permet aux constructeurs et autres fonctions de prendre les listes d'initialisation comme paramètres. Par exemple:

class SequenceClass
{
public:
    SequenceClass(std::initializer_list<int> list);
};

Cela permet SequenceClassd'être construit à partir d'une séquence d'entiers, tels que :

SequenceClass some_var = {1, 4, 5, 6};

Ce constructeur est un type spécial de constructeur, appelé initializer-list-constructor. Les classes avec un tel constructeur sont traitées spécialement lors de l'initialisation uniforme (voir ci - dessous )

La classe modèle std::initializer_list<>est un type de bibliothèque standard C++11 de première classe . Ils peuvent être construits statiquement par le compilateur C++11 via l'utilisation de la {}syntaxe sans nom de type dans des contextes où de telles accolades seront déduites d'un std::initializer_list, ou en spécifiant explicitement le type like std::initializer_list<SomeType>{args}(et ainsi de suite pour d'autres variétés de syntaxe de construction).

La liste peut être copiée une fois construite, ce qui est bon marché et agira comme une copie par référence (la classe est généralement implémentée comme une paire de pointeurs de début/de fin). An std::initializer_listest constant : ses membres ne peuvent pas être modifiés une fois qu'il est créé, et les données de ces membres ne peuvent pas non plus être modifiées (ce qui exclut de les déplacer, d'exiger des copies dans les membres de la classe, etc.).

Bien que sa construction soit spécialement traitée par le compilateur, an std::initializer_listest un type réel et peut donc être utilisé ailleurs que dans les constructeurs de classes. Les fonctions régulières peuvent prendre des std::initializer_lists tapés comme arguments. Par exemple:

void function_name(std::initializer_list<float> list); // Copying is cheap; see above

function_name({1.0f, -3.45f, -0.4f});

Des exemples de ceci dans la bibliothèque standard incluent les modèles std::min()et std::max()prenant des std::initializer_lists de type numérique.

Les conteneurs standard peuvent également être initialisés de ces manières :

std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v({ "xyzzy", "plugh", "abracadabra" });
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" }; // see "Uniform initialization" below

Initialisation uniforme

C++03 a un certain nombre de problèmes avec l'initialisation des types. Il existe plusieurs façons de le faire, et certaines produisent des résultats différents lorsqu'elles sont interchangées. La syntaxe traditionnelle du constructeur, par exemple, peut ressembler à une déclaration de fonction, et des mesures doivent être prises pour s'assurer que la règle d' analyse la plus vexante du compilateur ne la confondra pas avec une telle. Seuls les agrégats et les types de POD peuvent être initialisés avec des initialiseurs d'agrégats (à l'aide de SomeType var = {/*stuff*/};).

C++11 fournit une syntaxe qui permet une initialisation de type totalement uniforme qui fonctionne sur n'importe quel objet. Il développe la syntaxe de la liste d'initialisation :

struct BasicStruct
{
    int x;
    double y;
};

struct AltStruct
{
    AltStruct(int x, double y)
        : x_{x}
        , y_{y}
    {}

private:
    int x_;
    double y_;
};

BasicStruct var1{5, 3.2};
AltStruct var2{2, 4.3};

L'initialisation de var1se comporte exactement comme s'il s'agissait d'une initialisation agrégée. C'est-à-dire que chaque membre de données d'un objet, à son tour, sera initialisé par copie avec la valeur correspondante de la liste d'initialisation. La conversion de type implicite sera utilisée si nécessaire. S'il n'existe aucune conversion ou qu'il n'existe qu'une conversion restrictive, le programme est mal formé. L'initialisation de var2invoque le constructeur.

On peut aussi faire ceci :

struct IdString
{
    std::string name;
    int identifier;
};

IdString get_string()
{
    return {"foo", 42}; //Note the lack of explicit type.
}

L'initialisation uniforme ne remplace pas la syntaxe du constructeur, qui est encore parfois nécessaire. Si une classe a un constructeur de liste d'initialisation ( TypeName(initializer_list<SomeType>);), alors elle est prioritaire sur les autres formes de construction, à condition que la liste d'initialisation soit conforme au type du constructeur de séquence. La version C++11 de std::vectora un constructeur de liste d'initialisation pour son type de modèle. Ainsi ce code :

std::vector<int> the_vec{4};

appellera le constructeur de la liste d'initialisation, pas le constructeur de std::vectorqui prend un seul paramètre de taille et crée le vecteur avec cette taille. Pour accéder à ce dernier constructeur, l'utilisateur devra utiliser directement la syntaxe du constructeur standard.

Inférence de type

En C++03 (et C), pour utiliser une variable, son type doit être spécifié explicitement. Cependant, avec l'avènement des types de modèles et des techniques de métaprogrammation de modèles, le type de quelque chose, en particulier la valeur de retour bien définie d'une fonction, peut ne pas être facilement exprimé. Ainsi, le stockage d'intermédiaires dans des variables est difficile, nécessitant éventuellement une connaissance des éléments internes d'une bibliothèque de métaprogrammation donnée.

C++11 permet d'atténuer cela de deux manières. Premièrement, la définition d'une variable avec une initialisation explicite peut utiliser le automot - clé. Cela crée une variable du type spécifique de l'initialiseur :

auto some_strange_callable_type = std::bind(&some_function, _2, _1, some_object);
auto other_variable = 5;

Le type de some_strange_callable_typeest simplement quel que soit le remplacement de la fonction de modèle particulier des std::bindretours pour ces arguments particuliers. Ce type est facilement déterminé de manière procédurale par le compilateur dans le cadre de ses tâches d'analyse sémantique, mais n'est pas facile à déterminer pour l'utilisateur lors de l'inspection. Le type de other_variableest également bien défini, mais il est plus facile à déterminer pour l'utilisateur. C'est un int, qui est du même type que le littéral entier.

Cette utilisation du mot-clé autoen C++ réutilise la sémantique de ce mot-clé, qui était à l'origine utilisé dans le langage prédécesseur sans type B dans un rôle connexe de dénotant une définition de variable automatique non typée .

De plus, le mot-clé decltypepeut être utilisé pour déterminer le type d'expression au moment de la compilation. Par exemple:

int some_int;
decltype(some_int) other_integer_variable = 5;

Ceci est plus utile en conjonction avec auto, puisque le type de variable automatique n'est connu que du compilateur. Cependant, cela decltypepeut également être très utile pour les expressions dans le code qui font un usage intensif de la surcharge d'opérateurs et des types spécialisés.

autoest également utile pour réduire la verbosité du code. Par exemple, au lieu d'écrire

for (std::vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

le programmeur peut utiliser le plus court

for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

qui peut être encore compacté puisque "myvec" implémente des itérateurs de début/de fin :

for (const auto& x : myvec)

Cette différence augmente au fur et à mesure que le programmeur commence à imbriquer des conteneurs, bien que dans de tels cas, les typedefs soient un bon moyen de réduire la quantité de code.

Le type noté par decltypepeut être différent du type déduit par auto.

#include <vector>
int main()
{
    const std::vector<int> v(1);
    auto a = v[0];        // a has type int
    decltype(v[0]) b = 1; // b has type const int&, the return type of
                          //   std::vector<int>::operator[](size_type) const
    auto c = 0;           // c has type int
    auto d = c;           // d has type int
    decltype(c) e;        // e has type int, the type of the entity named by c
    decltype((c)) f = c;  // f has type int&, because (c) is an lvalue
    decltype(0) g;        // g has type int, because 0 is an rvalue
}

Boucle for basée sur la plage

C++11 étend la syntaxe de l' forinstruction pour permettre une itération facile sur une gamme d'éléments :

int my_array[5] = {1, 2, 3, 4, 5};
// double the value of each element in my_array:
for (int& x : my_array)
    x *= 2;

// similar but also using type inference for array elements
for (auto& x : my_array)
    x *= 2;

Cette forme de for, appelée « basée sur la plage pour », itérera sur chaque élément de la liste. Cela fonctionnera pour les tableaux de style C, les listes d'initialisation et tout type pour lequel des fonctions begin()et des end()fonctions sont définies et qui renvoient des itérateurs. Tous les conteneurs de bibliothèque standard qui ont des paires début/fin fonctionneront avec l'instruction for basée sur la plage.

Fonctions et expressions lambda

C++11 offre la possibilité de créer des fonctions anonymes , appelées fonctions lambda. Ceux-ci sont définis comme suit :

[](int x, int y) -> int { return x + y; }

Le type de retour ( -> intdans cet exemple) peut être omis tant que toutes les returnexpressions retournent le même type. Un lambda peut éventuellement être une fermeture .

Syntaxe de fonction alternative

La syntaxe de déclaration de fonction C standard était parfaitement adéquate pour l'ensemble des fonctionnalités du langage C. Comme le C++ a évolué à partir du C, il a conservé la syntaxe de base et l'a étendu là où c'était nécessaire. Cependant, au fur et à mesure que le C++ devenait plus complexe, il exposait plusieurs limites, notamment en ce qui concerne les déclarations de fonctions de modèle. Par exemple, en C++03, ceci est interdit :

template<class Lhs, class Rhs>
  Ret adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} //Ret must be the type of lhs+rhs

Le type Retest quel que soit l'ajout de types Lhset Rhsproduira. Même avec la fonctionnalité C++11 susmentionnée de decltype, cela n'est pas possible :

template<class Lhs, class Rhs>
  decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;} //Not valid C++11

Ce n'est pas du C++ valide car lhset rhsn'a pas encore été défini ; ils ne seront pas des identifiants valides tant que l'analyseur n'aura pas analysé le reste du prototype de fonction.

Pour contourner ce problème, C++11 a introduit une nouvelle syntaxe de déclaration de fonction, avec un type de retour de fin :

template<class Lhs, class Rhs>
  auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

Cette syntaxe peut être utilisée pour des déclarations et des définitions de fonctions plus banales :

struct SomeStruct
{
    auto func_name(int x, int y) -> int;
};

auto SomeStruct::func_name(int x, int y) -> int
{
    return x + y;
}

L'utilisation du mot-clé « auto » dans ce cas n'est qu'une partie de la syntaxe et n'effectue pas de déduction de type automatique en C++11. Cependant, à partir de C++14, le type de retour de fin peut être entièrement supprimé et le compilateur déduira automatiquement le type de retour.

Amélioration de la construction des objets

En C++03, les constructeurs d'une classe ne sont pas autorisés à appeler d'autres constructeurs dans une liste d'initialisation de cette classe. Chaque constructeur doit construire lui-même tous ses membres de classe ou appeler une fonction membre commune, comme suit :

class SomeType
{
public:
    SomeType(int new_number)
    {
        Construct(new_number);
    }

    SomeType()
    {
        Construct(42);
    }

private:
    void Construct(int new_number)
    {
        number = new_number;
    }

    int number;
};

Les constructeurs des classes de base ne peuvent pas être directement exposés aux classes dérivées ; chaque classe dérivée doit implémenter des constructeurs même si un constructeur de classe de base serait approprié. Les données membres non constantes des classes ne peuvent pas être initialisées sur le site de la déclaration de ces membres. Ils ne peuvent être initialisés que dans un constructeur.

C++11 fournit des solutions à tous ces problèmes.

C++11 permet aux constructeurs d'appeler d'autres constructeurs homologues (appelé délégation ). Cela permet aux constructeurs d'utiliser le comportement d'un autre constructeur avec un minimum de code ajouté. La délégation a été utilisée dans d'autres langages, par exemple Java et Objective-C .

Cette syntaxe est la suivante :

class SomeType
{
    int number;

public:
    SomeType(int new_number) : number(new_number) {}
    SomeType() : SomeType(42) {}
};

Notez que, dans ce cas, le même effet aurait pu être obtenu en définissant new_numberun paramètre par défaut. La nouvelle syntaxe, cependant, permet à la valeur par défaut (42) d'être exprimée dans l'implémentation plutôt que dans l'interface - un avantage pour les mainteneurs du code de la bibliothèque puisque les valeurs par défaut des paramètres de fonction sont « incrustées » pour appeler les sites, tandis que la délégation du constructeur permet la valeur à modifier sans recompilation du code à l'aide de la bibliothèque.

Cela vient avec une mise en garde : C++03 considère qu'un objet est construit lorsque son constructeur a fini de s'exécuter, mais C++11 considère un objet construit une fois qu'un constructeur a terminé son exécution. Étant donné que plusieurs constructeurs seront autorisés à s'exécuter, cela signifie que chaque constructeur délégant s'exécutera sur un objet entièrement construit de son propre type. Les constructeurs de classes dérivées s'exécuteront une fois que toutes les délégations dans leurs classes de base seront terminées.

Pour les constructeurs de classe de base, C++11 permet à une classe de spécifier que les constructeurs de classe de base seront hérités. Ainsi, le compilateur C++11 générera du code pour effectuer l'héritage et le transfert de la classe dérivée vers la classe de base. Il s'agit d'une fonctionnalité tout ou rien : soit tous les constructeurs de cette classe de base sont transmis, soit aucun d'entre eux ne l'est. De plus, un constructeur hérité sera masqué s'il correspond à la signature d'un constructeur de la classe dérivée, et des restrictions existent pour l'héritage multiple : les constructeurs de classe ne peuvent pas être hérités de deux classes qui utilisent des constructeurs avec la même signature .

La syntaxe est la suivante :

class BaseClass
{
public:
    BaseClass(int value);
};

class DerivedClass : public BaseClass
{
public:
    using BaseClass::BaseClass;
};

Pour l'initialisation des membres, C++11 autorise cette syntaxe :

class SomeClass
{
public:
    SomeClass() {}
    explicit SomeClass(int new_value) : value(new_value) {}

private:
    int value = 5;
};

Tout constructeur de la classe s'initialisera valueavec 5, si le constructeur ne remplace pas l'initialisation par la sienne. Ainsi, le constructeur vide ci-dessus s'initialisera valuecomme l'indique la définition de classe, mais le constructeur qui prend un int l'initialisera au paramètre donné.

Il peut également utiliser un constructeur ou une initialisation uniforme, au lieu de l'initialisation d'affectation illustrée ci-dessus.

Remplacements explicites et final

En C++03, il est possible de créer accidentellement une nouvelle fonction virtuelle, alors que l'on avait l'intention de remplacer une fonction de classe de base. Par exemple:

struct Base
{
    virtual void some_func(float);
};

struct Derived : Base
{
    virtual void some_func(int);
};

Supposons que le Derived::some_funcsoit destiné à remplacer la version de la classe de base. Mais au lieu de cela, parce qu'il a une signature différente , il crée une deuxième fonction virtuelle. C'est un problème courant, en particulier lorsqu'un utilisateur va modifier la classe de base.

C++11 fournit une syntaxe pour résoudre ce problème.

struct Base
{
    virtual void some_func(float);
};

struct Derived : Base
{
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

L' overrideidentifiant spécial signifie que le compilateur vérifiera la ou les classes de base pour voir s'il existe une fonction virtuelle avec cette signature exacte. Et s'il n'y en a pas, le compilateur indiquera une erreur.

C++11 ajoute également la possibilité d'empêcher l'héritage des classes ou simplement d'empêcher la substitution de méthodes dans les classes dérivées. Cela se fait avec l'identifiant spécial final. Par exemple:

struct Base1 final { };

struct Derived1 : Base1 { }; // ill-formed because the class Base1 has been marked final
struct Base2
{
    virtual void f() final;
};

struct Derived2 : Base2
{
    void f(); // ill-formed because the virtual function Base2::f has been marked final
};

Dans cet exemple, l' virtual void f() final;instruction déclare une nouvelle fonction virtuelle, mais elle empêche également les classes dérivées de la remplacer. Cela a également pour effet d'empêcher les classes dérivées d'utiliser cette combinaison de nom de fonction et de paramètre particulière.

Notez que overrideni finalles mots-clés de langue ne le sont. Ce sont techniquement des identifiants pour les attributs du déclarant :

  • ils acquièrent une signification particulière en tant qu'attributs uniquement lorsqu'ils sont utilisés dans ces contextes de fin spécifiques (après tous les spécificateurs de type, les spécificateurs d'accès, les déclarations de membres (pour les types struct, class et enum) et les spécificateurs de déclarateur, mais avant l'initialisation ou l'implémentation du code de chaque déclarant dans une virgule -liste séparée des déclarants);
  • ils ne modifient pas la signature de type déclarée et ne déclarent ni ne remplacent aucun nouvel identifiant dans aucune portée ;
  • les attributs de déclarant reconnus et acceptés peuvent être étendus dans les futures versions de C++ (certaines extensions spécifiques au compilateur reconnaissent déjà les attributs de déclarant ajoutés, pour fournir des options de génération de code ou des conseils d'optimisation au compilateur, ou pour générer des données ajoutées dans le code compilé, destiné à débogueurs, lieurs et déploiement du code compilé, ou pour fournir des attributs de sécurité supplémentaires spécifiques au système, ou pour améliorer les capacités de réflexion au moment de l'exécution, ou pour fournir des informations de liaison supplémentaires pour l'interopérabilité avec d'autres langages de programmation et systèmes d'exécution ; ces extensions peuvent prendre des paramètres entre parenthèses après l'identifiant de l'attribut déclarateur ; pour la conformité ANSI, ces extensions spécifiques au compilateur doivent utiliser la convention de préfixe de double soulignement).
  • Dans tout autre endroit, ils peuvent être des identifiants valides pour de nouvelles déclarations (et une utilisation ultérieure s'ils sont accessibles).

Constante de pointeur nulle

Aux fins de cette section et de cette section uniquement, chaque occurrence de « 0» est signifiée comme « une expression constante qui évalue à 0, qui est de type int ». En réalité, l'expression constante peut être de n'importe quel type intégral.

Depuis l'aube de C en 1972, la constante 0a le double rôle de constante entière et de constante pointeur nulle. L'ambiguïté inhérente au double sens de a 0été traitée en C en utilisant la macro du préprocesseur NULL, qui se développe généralement en soit ((void*)0)ou 0. C++ interdit la conversion implicite de void *vers d'autres types de pointeur, supprimant ainsi l'avantage du transtypage 0vers void *. Par conséquent, seule 0est autorisée en tant que constante de pointeur nul. Cela interagit mal avec la surcharge de fonction :

void foo(char *);
void foo(int);

If NULLest défini comme 0(ce qui est généralement le cas en C++), l'instruction foo(NULL);appellera foo(int), ce qui n'est presque certainement pas ce que le programmeur voulait, et ce n'est pas ce que suggère une lecture superficielle du code.

C++11 corrige cela en introduisant un nouveau mot-clé pour servir de constante de pointeur nul distingué : nullptr. Il est de type nullptr_t, qui est implicitement convertible et comparable à n'importe quel type pointeur ou pointeur sur membre. Il n'est pas implicitement convertible ou comparable aux types intégraux, à l'exception de bool. Alors que la proposition originale précisait qu'une valeur r de type nullptr_tne devrait pas être convertible en bool, le groupe de travail principal sur le langage a décidé qu'une telle conversion serait souhaitable, par souci de cohérence avec les types de pointeurs réguliers. Les changements de formulation proposés ont été votés à l'unanimité dans le document de travail en juin 2008. Une proposition similaire est également présentée au groupe de travail sur la norme C.

Pour des raisons de compatibilité descendante, 0reste une constante de pointeur null valide.

char *pc = nullptr;     // OK
int  *pi = nullptr;     // OK
bool   b = nullptr;     // OK. b is false.
int    i = nullptr;     // error

foo(nullptr);           // calls foo(nullptr_t), not foo(int);
/*
  Note that foo(nullptr_t) will actually call foo(char *) in the example above using an implicit conversion,
  only if no other functions are overloading with compatible pointer types in scope.
  If multiple overloadings exist, the resolution will fail as it is ambiguous,
  unless there is an explicit declaration of foo(nullptr_t).

  In standard types headers for C++11, the nullptr_t  type should be declared as:
      typedef decltype(nullptr) nullptr_t;
  but not as:
      typedef int nullptr_t; // prior versions of C++ which need NULL to be defined as 0
      typedef void *nullptr_t; // ANSI C which defines NULL as ((void*)0)
*/

Énumérations fortement typées

En C++03, les énumérations ne sont pas de type sécurisé. Ce sont effectivement des entiers, même lorsque les types d'énumération sont distincts. Cela permet la comparaison entre deux valeurs d'énumération de types d'énumération différents. La seule sécurité fournie par C++03 est qu'un entier ou une valeur d'un type enum ne se convertit pas implicitement en un autre type enum. De plus, le type intégral sous-jacent est défini par l'implémentation ; le code qui dépend de la taille de l'énumération est donc non portable. Enfin, les valeurs d'énumération sont étendues à la portée englobante. Ainsi, il n'est pas possible pour deux énumérations distinctes dans la même portée d'avoir des noms de membres correspondants.

C++11 permet une classification spéciale de l'énumération qui n'a aucun de ces problèmes. Ceci est exprimé à l'aide de la déclaration enum class( enum structest également accepté comme synonyme) :

enum class Enumeration
{
    Val1,
    Val2,
    Val3 = 100,
    Val4 // = 101
};

Cette énumération est de type sécurisé. Les valeurs de classe Enum ne sont pas implicitement converties en entiers. Ainsi, ils ne peuvent pas non plus être comparés à des entiers (l'expression Enumeration::Val4 == 101donne une erreur de compilation).

Le type sous-jacent des classes enum est toujours connu. Le type par défaut est int; cela peut être remplacé par un type intégral différent comme on peut le voir dans cet exemple :

enum class Enum2 : unsigned int {Val1, Val2};

Avec les énumérations à l'ancienne, les valeurs sont placées dans la portée externe. Avec les énumérations de nouveau style, elles sont placées dans la portée du nom de classe enum. Ainsi, dans l'exemple ci-dessus, Val1n'est pas défini, mais Enum2::Val1est défini.

Il existe également une syntaxe de transition pour permettre aux énumérations à l'ancienne de fournir une portée explicite et la définition du type sous-jacent :

enum Enum3 : unsigned long {Val1 = 1, Val2};

Dans ce cas, les noms des énumérateurs sont définis dans la portée de l'énumération ( Enum3::Val1), mais pour une compatibilité descendante, ils sont également placés dans la portée englobante.

Les énumérations de déclaration directe sont également possibles en C++11. Auparavant, les types enum ne pouvaient pas être déclarés en avant car la taille de l'énumération dépend de la définition de ses membres. Tant que la taille de l'énumération est spécifiée implicitement ou explicitement, elle peut être déclarée :

enum Enum1;                      // Invalid in C++03 and C++11; the underlying type cannot be determined.
enum Enum2 : unsigned int;       // Valid in C++11, the underlying type is specified explicitly.
enum class Enum3;                // Valid in C++11, the underlying type is int.
enum class Enum4 : unsigned int; // Valid in C++11.
enum Enum2 : unsigned short;     // Invalid in C++11, because Enum2 was formerly declared with a different underlying type.

Support à angle droit

L'analyseur syntaxique de C++03 définit " >>" comme l'opérateur de décalage vers la droite ou l'opérateur d'extraction de flux dans tous les cas. Cependant, avec les déclarations de modèles imbriquées, le programmeur a tendance à négliger de placer un espace entre les deux crochets à angle droit, provoquant ainsi une erreur de syntaxe du compilateur.

C++11 améliore la spécification de l'analyseur afin que plusieurs crochets à angle droit soient interprétés comme fermant la liste d'arguments du modèle là où cela est raisonnable. Cela peut être remplacé en utilisant des parenthèses autour des expressions de paramètres à l'aide des opérateurs binaires " >", " >=" ou " >>" :

template<bool Test> class SomeType;
std::vector<SomeType<1>2>> x1;  // Interpreted as a std::vector of SomeType<true>,
    // followed by "2 >> x1", which is not valid syntax for a declarator. 1 is true.
std::vector<SomeType<(1>2)>> x1;  // Interpreted as std::vector of SomeType<false>,
    // followed by the declarator "x1", which is valid C++11 syntax. (1>2) is false.

Opérateurs de conversion explicites

C++98 a ajouté le explicitmot - clé comme modificateur sur les constructeurs pour empêcher les constructeurs à argument unique d'être utilisés comme opérateurs de conversion de type implicite. Cependant, cela ne fait rien pour les opérateurs de conversion réels. Par exemple, une classe de pointeur intelligent peut avoir un operator bool()pour lui permettre d'agir plus comme un pointeur primitif : si elle inclut cette conversion, elle peut être testée avec if (smart_ptr_variable)(ce qui serait vrai si le pointeur était non nul et faux sinon). Cependant, cela permet également d'autres conversions involontaires. Parce que C++ boolest défini comme un type arithmétique, il peut être implicitement converti en types intégraux ou même à virgule flottante, ce qui permet des opérations mathématiques qui ne sont pas prévues par l'utilisateur.

En C++11, le explicitmot - clé peut désormais être appliqué aux opérateurs de conversion. Comme pour les constructeurs, cela empêche l'utilisation de ces fonctions de conversion dans les conversions implicites. Cependant, les contextes de langage qui nécessitent spécifiquement une valeur booléenne (les conditions des instructions if et des boucles, et les opérandes des opérateurs logiques) comptent comme des conversions explicites et peuvent donc utiliser un opérateur de conversion bool.

Par exemple, cette fonctionnalité résout proprement le problème de bool sécurisé .

Alias ​​de modèle

En C++03, il est possible de définir un typedef uniquement comme synonyme d'un autre type, y compris un synonyme pour une spécialisation de modèle avec tous les arguments de modèle réels spécifiés. Il n'est pas possible de créer un modèle typedef. Par exemple:

template <typename First, typename Second, int Third>
class SomeType;

template <typename Second>
typedef SomeType<OtherType, Second, 5> TypedefName; // Invalid in C++03

Cela ne compilera pas.

C++11 ajoute cette capacité avec cette syntaxe :

template <typename First, typename Second, int Third>
class SomeType;

template <typename Second>
using TypedefName = SomeType<OtherType, Second, 5>;

La usingsyntaxe peut également être utilisée comme alias de type en C++11 :

typedef void (*FunctionType)(double);       // Old style
using FunctionType = void (*)(double); // New introduced syntax

Syndicats sans restriction

En C++03, il existe des restrictions sur les types d'objets pouvant être membres d'un fichier union. Par exemple, les unions ne peuvent pas contenir d'objets qui définissent un constructeur ou un destructeur non trivial. C++11 lève certaines de ces restrictions.

Si un unionmembre a une fonction membre spéciale non triviale , le compilateur ne générera pas la fonction membre équivalente pour unionet elle doit être définie manuellement.

Voici un exemple simple d'union autorisée en C++11 :

#include <new> // Needed for placement 'new'.

struct Point
{
    Point() {}
    Point(int x, int y): x_(x), y_(y) {} 
    int x_, y_;
};

union U
{
    int z;
    double w;
    Point p; // Invalid in C++03; valid in C++11.
    U() {} // Due to the Point member, a constructor definition is now needed.
    U(const Point& pt) : p(pt) {} // Construct Point object using initializer list.
    U& operator=(const Point& pt) { new(&p) Point(pt); return *this; } // Assign Point object using placement 'new'.
};

Les modifications ne briseront aucun code existant car elles ne font qu'assouplir les règles actuelles.

Améliorations des fonctionnalités de base du langage

Ces fonctionnalités permettent au langage de faire des choses qui étaient auparavant impossibles, excessivement verbeuses ou qui nécessitaient des bibliothèques non portables.

Modèles variadiques

En C++11, les modèles peuvent prendre un nombre variable de paramètres de modèle. Cela permet également la définition de fonctions variadiques de type sûr .

Nouveaux littéraux de chaîne

C++03 propose deux types de littéraux de chaîne . Le premier kind, contenu entre guillemets doubles, produit un tableau de type const char. Le deuxième type, défini comme L"", produit un tableau terminé par NULL de type const wchar_t, où wchar_test un caractère large de taille et de sémantique indéfinies. Aucun des deux types de littéraux ne prend en charge les littéraux de chaîne avec UTF-8 , UTF-16 ou tout autre type de codage Unicode .

La définition du type chara été modifiée pour exprimer explicitement qu'il a au moins la taille nécessaire pour stocker un codage UTF-8 sur huit bits et suffisamment grand pour contenir n'importe quel membre du jeu de caractères d'exécution de base du compilateur. Il était auparavant défini comme étant uniquement ce dernier dans le standard C++ lui-même, s'appuyant alors sur le standard C pour garantir au moins 8 bits.

C++11 prend en charge trois encodages Unicode : UTF-8 , UTF-16 et UTF-32 . En plus des modifications précédemment notées apportées à la définition de char, C++11 ajoute deux nouveaux types de caractères : char16_tet char32_t. Ceux-ci sont conçus pour stocker respectivement UTF-16 et UTF-32.

La création de littéraux de chaîne pour chacun de ces encodages peut être effectuée ainsi :

u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."

Le type de la première chaîne est le type habituel const char[]. Le type de la deuxième chaîne est const char16_t[](notez le préfixe « u » minuscule). Le type de la troisième chaîne est const char32_t[](préfixe 'U' majuscule).

Lors de la création de littéraux de chaîne Unicode, il est souvent utile d'insérer des points de code Unicode directement dans la chaîne. Pour ce faire, C++11 autorise cette syntaxe :

u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \U00002018."

Le nombre après le \uest un nombre hexadécimal ; il n'a pas besoin du 0xpréfixe habituel . L'identifiant \ureprésente un point de code Unicode 16 bits ; pour entrer un point de code de 32 bits, utilisez \Uet un nombre hexadécimal de 32 bits. Seuls les points de code Unicode valides peuvent être saisis. Par exemple, les points de code sur la plage U+D800–U+DFFF sont interdits, car ils sont réservés aux paires de substitution dans les codages UTF-16.

Il est également parfois utile d'éviter l'échappement manuel des chaînes, en particulier pour l'utilisation de littéraux de fichiers XML , de langages de script ou d'expressions régulières. C++11 fournit un littéral de chaîne brut :

R"(The String Data \ Stuff " )"
R"delimiter(The String Data \ Stuff " )delimiter"

Dans le premier cas, tout ce qui se trouve entre le "(et le )"fait partie de la chaîne. Les caractères "et \n'ont pas besoin d'être échappés. Dans le second cas, le "delimiter(commence la chaîne, et il ne se termine que lorsque )delimiter"est atteint. La chaîne delimiterpeut être n'importe quelle chaîne jusqu'à 16 caractères, y compris la chaîne vide. Cette chaîne ne peut pas contenir d'espaces, de caractères de contrôle (, ), ou le \caractère. L'utilisation de cette chaîne de délimiteur permet à l'utilisateur d'avoir des )caractères dans des littéraux de chaîne bruts. Par exemple, R"delimiter((a-z))delimiter"équivaut à "(a-z)".

Les littéraux de chaîne bruts peuvent être combinés avec le littéral large ou l'un des préfixes littéraux Unicode :

u8R"XXX(I'm a "raw UTF-8" string.)XXX"
uR"*(This is a "raw UTF-16" string.)*"
UR"(This is a "raw UTF-32" string.)"

Littéraux définis par l'utilisateur

C++03 fournit un certain nombre de littéraux. Les caractères 12.5sont un littéral qui est résolu par le compilateur en tant que type doubleavec la valeur 12,5. Cependant, l'ajout du suffixe f, comme dans 12.5f, crée une valeur de type floatqui contient la valeur 12,5. Les modificateurs de suffixe pour les littéraux sont fixés par la spécification C++ et le code C++03 ne peut pas créer de nouveaux modificateurs de littéraux.

En revanche, C++11 permet à l'utilisateur de définir de nouveaux types de modificateurs littéraux qui construiront des objets en fonction de la chaîne de caractères que le littéral modifie.

La transformation des littéraux est redéfinie en deux phases distinctes : cru et cuit. Un littéral brut est une séquence de caractères d'un type spécifique, tandis que le littéral cuit est d'un type distinct. Le littéral C++ 1234, en tant que littéral brut, est cette séquence de caractères '1', '2', '3', '4'. En tant que littéral cuit, il s'agit de l'entier 1234. Le littéral C++ 0xAsous forme brute est '0', 'x', 'A', tandis que sous forme cuite, il s'agit de l'entier 10.

Les littéraux peuvent être étendus à la fois sous forme brute et cuite, à l'exception des littéraux de chaîne, qui ne peuvent être traités que sous forme cuite. Cette exception est due au fait que les chaînes ont des préfixes qui affectent la signification et le type spécifiques des caractères en question.

Tous les littéraux définis par l'utilisateur sont des suffixes ; la définition de littéraux de préfixe n'est pas possible. Tous les suffixes commençant par n'importe quel caractère à l'exception du trait de soulignement ( _) sont réservés par la norme. Ainsi, tous les littéraux définis par l'utilisateur doivent avoir des suffixes commençant par un trait de soulignement ( _).

Les littéraux définis par l'utilisateur traitant la forme brute du littéral sont définis via un opérateur de littéral, qui s'écrit operator "". Un exemple suit :

OutputType operator "" _mysuffix(const char * literal_string)
{
    // assumes that OutputType has a constructor that takes a const char *
    OutputType ret(literal_string);
    return ret;
}

OutputType some_variable = 1234_mysuffix;
// assumes that OutputType has a get_value() method that returns a double
assert(some_variable.get_value() == 1234.0)

L'instruction d'affectation OutputType some_variable = 1234_mysuffix;exécute le code défini par la fonction littérale définie par l'utilisateur. Cette fonction est passée "1234"sous forme de chaîne de style C, elle a donc un terminateur nul.

Un mécanisme alternatif pour le traitement des littéraux bruts entiers et à virgule flottante est via un modèle variadique :

template<char...> OutputType operator "" _tuffix();

OutputType some_variable = 1234_tuffix;
OutputType another_variable = 2.17_tuffix;

Cela instancie la fonction de traitement littérale sous la forme operator "" _tuffix<'1', '2', '3', '4'>(). Sous cette forme, il n'y a pas de caractère nul terminant la chaîne. L'objectif principal est d'utiliser le constexprmot - clé de C++11 pour s'assurer que le compilateur transformera entièrement le littéral au moment de la compilation, en supposant qu'il OutputTypes'agit d'un type constexpr-constructible et copiable, et que la fonction de traitement du littéral est une constexprfonction.

Pour les littéraux numériques, le type du littéral cuit est soit unsigned long longpour les littéraux intégraux, soit pour les littéraux long doubleà virgule flottante. (Remarque : les types intégraux signés ne sont pas nécessaires car un littéral à préfixe de signe est analysé comme une expression contenant le signe en tant qu'opérateur de préfixe unaire et le nombre non signé.) Il n'y a pas de forme de modèle alternative :

OutputType operator "" _suffix(unsigned long long);
OutputType operator "" _suffix(long double);

OutputType some_variable = 1234_suffix; // Uses the 'unsigned long long' overload.
OutputType another_variable = 3.1416_suffix; // Uses the 'long double' overload.

En accord avec les nouveaux préfixes de chaîne précédemment mentionnés, pour les littéraux de chaîne, ceux-ci sont utilisés :

OutputType operator "" _ssuffix(const char     * string_values, size_t num_chars);
OutputType operator "" _ssuffix(const wchar_t  * string_values, size_t num_chars);
OutputType operator "" _ssuffix(const char16_t * string_values, size_t num_chars);
OutputType operator "" _ssuffix(const char32_t * string_values, size_t num_chars);

OutputType some_variable =   "1234"_ssuffix; // Uses the 'const char *' overload.
OutputType some_variable = u8"1234"_ssuffix; // Uses the 'const char *' overload.
OutputType some_variable =  L"1234"_ssuffix; // Uses the 'const wchar_t *'  overload.
OutputType some_variable =  u"1234"_ssuffix; // Uses the 'const char16_t *' overload.
OutputType some_variable =  U"1234"_ssuffix; // Uses the 'const char32_t *' overload.

Il n'y a pas de formulaire de modèle alternatif. Les littéraux de caractère sont définis de la même manière.

Modèle de mémoire multithread

C++11 standardise la prise en charge de la programmation multithread .

Il y a deux parties impliquées : un modèle de mémoire qui permet à plusieurs threads de coexister dans un programme et un support de bibliothèque pour l'interaction entre les threads. (Voir la section de cet article sur les fonctionnalités de threading .)

Le modèle de mémoire définit quand plusieurs threads peuvent accéder au même emplacement mémoire et spécifie quand les mises à jour d'un thread deviennent visibles pour les autres threads.

Stockage local de thread

Dans un environnement multithread, il est courant que chaque thread ait des variables uniques . Cela se produit déjà pour les variables locales d'une fonction, mais cela ne se produit pas pour les variables globales et statiques.

Une nouvelle durée de stockage thread-local (en plus des static , dynamic et automatic existants ) est indiquée par le spécificateur de stockage thread_local.

Tout objet qui pourrait avoir une durée de stockage statique (c'est-à-dire une durée de vie couvrant toute l'exécution du programme) peut se voir attribuer une durée locale de thread à la place. L'intention est que, comme toute autre variable de durée statique, un objet thread-local puisse être initialisé à l'aide d'un constructeur et détruit à l'aide d'un destructeur.

Fonctions membres spéciales explicitement définies par défaut et supprimées

En C++03, le compilateur fournit, pour les classes qui ne les fournissent pas pour elles-mêmes, un constructeur par défaut, un constructeur de copie, un opérateur d'affectation de copie ( operator=) et un destructeur. Le programmeur peut remplacer ces valeurs par défaut en définissant des versions personnalisées. C++ définit également plusieurs opérateurs globaux (tels que operator new) qui fonctionnent sur toutes les classes, que le programmeur peut remplacer.

Cependant, il y a très peu de contrôle sur la création de ces valeurs par défaut. Rendre une classe intrinsèquement non copiable, par exemple, nécessite de déclarer un constructeur de copie privée et un opérateur d'affectation de copie et de ne pas les définir. Tenter d'utiliser ces fonctions est une violation de la règle de définition unique (ODR). Bien qu'un message de diagnostic ne soit pas requis, les violations peuvent entraîner une erreur de l'éditeur de liens.

Dans le cas du constructeur par défaut, le compilateur ne générera pas de constructeur par défaut si une classe est définie avec des constructeurs. C'est utile dans de nombreux cas, mais il est également utile de pouvoir avoir à la fois des constructeurs spécialisés et la valeur par défaut générée par le compilateur.

C++11 permet la définition par défaut et la suppression explicites de ces fonctions membres spéciales. Par exemple, ce type déclare explicitement qu'il utilise le constructeur par défaut :

struct SomeType
{
    SomeType() = default; //The default constructor is explicitly stated.
    SomeType(OtherType value);
};

Alternativement, certaines fonctionnalités peuvent être explicitement désactivées. Par exemple, ce type n'est pas copiable :

struct NonCopyable
{
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

Le = deletespécificateur peut être utilisé pour interdire l'appel de toute fonction, ce qui peut être utilisé pour interdire l'appel d'une fonction membre avec des paramètres particuliers. Par exemple:

struct NoInt
{
    void f(double i);
    void f(int) = delete;
};

Une tentative d'appel f()avec un intsera rejetée par le compilateur, au lieu d'effectuer une conversion silencieuse en double. Cela peut être généralisé pour interdire l'appel de la fonction avec tout type autre que doublecelui-ci :

struct OnlyDouble
{
    void f(double d);
    template<class T> void f(T) = delete;
};

Taper long long int

En C++03, le plus grand type entier est long int. Il est garanti d'avoir au moins autant de bits utilisables que int. Cela a abouti à long intune taille de 64 bits sur certaines implémentations populaires et de 32 bits sur d'autres. C++11 ajoute un nouveau type entier long long intpour résoudre ce problème. Il est garanti qu'il est au moins aussi grand qu'un long int, et qu'il n'a pas moins de 64 bits. Le type a été introduit à l'origine par C99 dans le C standard, et la plupart des compilateurs C++ le supportaient déjà en tant qu'extension.

Assertions statiques

C++03 fournit deux méthodes pour tester les assertions : la macro assertet la directive du préprocesseur #error. Cependant, ni l'un ni l'autre n'est approprié pour une utilisation dans les modèles : la macro teste l'assertion au moment de l'exécution, tandis que la directive du préprocesseur teste l'assertion pendant le prétraitement, qui se produit avant l'instanciation des modèles. Ni l'un ni l'autre n'est approprié pour tester les propriétés qui dépendent des paramètres du modèle.

Le nouvel utilitaire introduit une nouvelle façon de tester les assertions au moment de la compilation, en utilisant le nouveau mot-clé static_assert. La déclaration prend cette forme :

static_assert (constant-expression, error-message);

Voici quelques exemples d' static_assertutilisation :

static_assert((GREEKPI > 3.14) && (GREEKPI < 3.15), "GREEKPI is inaccurate!");
template<class T>
struct Check
{
    static_assert(sizeof(int) <= sizeof(T), "T is not big enough!");
};
template<class Integral>
Integral foo(Integral x, Integral y)
{
    static_assert(std::is_integral<Integral>::value, "foo() parameter must be an integral type.");
}

Lorsque l'expression constante est falsele compilateur produit un message d'erreur. Le premier exemple est similaire à la directive préprocesseur #error, bien que le préprocesseur ne supporte que les types intégraux. En revanche, dans le deuxième exemple, l'assertion est vérifiée à chaque instanciation de la classe template Check.

Les assertions statiques sont également utiles en dehors des modèles. Par exemple, une implémentation donnée d'un algorithme peut dépendre de la taille d'un long longétant plus grand qu'un int, ce que la norme ne garantit pas. Une telle hypothèse est valable sur la plupart des systèmes et compilateurs, mais pas tous.

Permet sizeofde travailler sur des membres de classes sans objet explicite

En C++03, l' sizeofopérateur peut être utilisé sur les types et les objets. Mais il ne peut pas être utilisé pour faire ceci:

struct SomeType { OtherType member; };

sizeof(SomeType::member); // Does not work with C++03. Okay with C++11

Cela devrait renvoyer la taille de OtherType. C++03 interdit cela, il s'agit donc d'une erreur de compilation. C++11 le permet. Il est également autorisé pour l' alignofopérateur introduit en C++11.

Alignement des objets de contrôle et de requête

C++11 permet d'interroger et de contrôler l'alignement des variables avec alignofet alignas.

L' alignofopérateur prend le type et renvoie la limite de puissance de 2 octets sur laquelle les instances de type doivent être allouées (en tant que std::size_t). Lorsqu'un type de référence est donné, alignofrenvoie l'alignement du type référencé ; pour les tableaux, il renvoie l'alignement du type d'élément.

Le alignasspécificateur contrôle l'alignement de la mémoire pour une variable. Le spécificateur prend une constante ou un type ; lorsqu'il est fourni, un type alignas(T)est un raccourci pour alignas(alignof(T)). Par exemple, pour spécifier qu'un tableau de caractères doit être correctement aligné pour contenir un flottant :

alignas(float) unsigned char c[sizeof(float)]

Autoriser les implémentations de récupération de place

Les normes C++ antérieures fournissaient le ramasse-miettes piloté par le programmeur via set_new_handler, mais ne donnaient aucune définition de l'accessibilité des objets à des fins de ramasse-miettes automatique. C++11 définit les conditions dans lesquelles les valeurs de pointeur sont "dérivées en toute sécurité" d'autres valeurs. Une implémentation peut spécifier qu'elle fonctionne sous stricte sécurité des pointeurs , auquel cas les pointeurs qui ne sont pas dérivés selon ces règles peuvent devenir invalides.

Les attributs

C++11 fournit une syntaxe standardisée pour les extensions de compilateur/outil du langage. De telles extensions étaient traditionnellement spécifiées à l'aide de #pragmamots-clés directifs ou spécifiques au fournisseur (comme __attribute__pour GNU et __declspecpour Microsoft). Avec la nouvelle syntaxe, les informations ajoutées peuvent être spécifiées sous la forme d'un attribut entouré de doubles crochets. Un attribut peut être appliqué à divers éléments du code source :

int [[attr1]] i [[attr2, attr3]];

[[attr4(arg1, arg2)]] if (cond)
{
    [[vendor::attr5]] return i;
}

Dans l'exemple ci - dessus, attribut attr1applique au type de variable i, attr2et attr3appliquer à la variable elle - même, attr4s'applique à la ifdéclaration et vendor::attr5applique à la déclaration de retour. En général (mais à quelques exceptions près), un attribut spécifié pour une entité nommée est placé après le nom, et avant l'entité sinon, comme indiqué ci-dessus, plusieurs attributs peuvent être répertoriés à l'intérieur d'une paire de doubles crochets, des arguments supplémentaires peuvent être fournis pour un attribut, et les attributs peuvent être délimités par des espaces de noms d'attributs spécifiques au fournisseur.

Il est recommandé que les attributs n'aient pas de signification sémantique de langage et ne modifient pas le sens d'un programme lorsqu'ils sont ignorés. Les attributs peuvent être utiles pour fournir des informations qui, par exemple, aident le compilateur à émettre de meilleurs diagnostics ou à optimiser le code généré.

C++11 fournit lui-même deux attributs standard : noreturnpour spécifier qu'une fonction ne retourne pas, et carries_dependencypour aider à optimiser le code multi-thread en indiquant que les arguments de la fonction ou la valeur de retour portent une dépendance.

Modifications de la bibliothèque standard C++

Un certain nombre de nouvelles fonctionnalités ont été introduites dans la bibliothèque standard C++11. Beaucoup d'entre eux auraient pu être implémentés sous l'ancien standard, mais certains reposent (dans une plus ou moins grande mesure) sur les nouvelles fonctionnalités de base de C++11.

Une grande partie des nouvelles bibliothèques a été définie dans le document C++ Standards Committee's Library Technical Report (appelé TR1), qui a été publié en 2005. Diverses implémentations complètes et partielles de TR1 sont actuellement disponibles en utilisant l'espace de noms std::tr1. Pour C++11, ils ont été déplacés vers l'espace de noms std. Cependant, comme les fonctionnalités TR1 ont été intégrées à la bibliothèque standard C++11, elles ont été mises à niveau le cas échéant avec des fonctionnalités du langage C++11 qui n'étaient pas disponibles dans la version TR1 initiale. En outre, ils peuvent avoir été améliorés avec des fonctionnalités qui étaient possibles sous C++03, mais ne faisaient pas partie de la spécification TR1 d'origine.

Mises à niveau vers des composants de bibliothèque standard

C++11 offre un certain nombre de nouvelles fonctionnalités de langage dont les composants de bibliothèque standard existants peuvent bénéficier. Par exemple, la plupart des conteneurs de bibliothèque standard peuvent bénéficier de la prise en charge du constructeur de déplacement basé sur les références Rvalue, à la fois pour déplacer rapidement des conteneurs lourds et pour déplacer le contenu de ces conteneurs vers de nouveaux emplacements mémoire. Les composants de bibliothèque standard ont été mis à niveau avec les nouvelles fonctionnalités du langage C++11, le cas échéant. Ceux-ci incluent, mais ne sont pas nécessairement limités à :

  • Les références Rvalue et le support de déplacement associé
  • Prise en charge de l'unité de codage UTF-16 et des types de caractères Unicode de l'unité de codage UTF-32
  • Modèles Variadic (couplés avec des références Rvalue pour permettre une transmission parfaite)
  • Expressions constantes au moment de la compilation
  • decltype
  • explicit opérateurs de conversion
  • Fonctions déclarées par défaut ou supprimées

De plus, beaucoup de temps s'est écoulé depuis la norme C++ précédente. Beaucoup de code utilisant la bibliothèque standard a été écrit. Cela a révélé des parties des bibliothèques standard qui pourraient être améliorées. Parmi les nombreux domaines d'amélioration envisagés figuraient les allocataires de bibliothèque standard . Un nouveau modèle d'allocateurs basé sur la portée a été inclus dans C++11 pour compléter le modèle précédent.

Installations de filetage

Alors que le langage C++03 fournit un modèle de mémoire qui prend en charge le threading, la principale prise en charge de l'utilisation réelle du threading est fournie avec la bibliothèque standard C++11.

Une classe de thread ( std::thread) est fournie, qui prend un objet fonction (et une série facultative d'arguments à lui transmettre) pour s'exécuter dans le nouveau thread. Il est possible de provoquer l'arrêt d'un thread jusqu'à la fin d'un autre thread en cours d'exécution, fournissant ainsi la prise en charge de la jonction de thread via la std::thread::join()fonction membre. L'accès est fourni, dans la mesure du possible, aux objets de thread natifs sous-jacents pour les opérations spécifiques à la plate-forme par la std::thread::native_handle()fonction membre.

Pour la synchronisation entre les threads, les mutex appropriés ( std::mutex, std::recursive_mutex, etc.) et les variables de condition ( std::condition_variableet std::condition_variable_any) sont ajoutés à la bibliothèque. Ceux-ci sont accessibles via les verrous RAII ( Resource Acquisition Is Initialization ) ( std::lock_guardet std::unique_lock) et les algorithmes de verrouillage pour une utilisation facile.

Pour un travail de bas niveau hautes performances, la communication entre les threads est parfois nécessaire sans la surcharge des mutex. Cela se fait à l'aide d' opérations atomiques sur les emplacements mémoire. Ceux-ci peuvent éventuellement spécifier les contraintes de visibilité mémoire minimales nécessaires pour une opération. Des barrières de mémoire explicites peuvent également être utilisées à cette fin.

La bibliothèque de threads C++11 comprend également des contrats à terme et des promesses pour la transmission de résultats asynchrones entre les threads et std::packaged_taskpour l'encapsulation d'un appel de fonction pouvant générer un tel résultat asynchrone. La proposition de contrats à terme a été critiquée car elle ne permet pas de combiner les contrats à terme et de vérifier la réalisation d'une promesse à l'intérieur d'un ensemble de promesses.

D'autres fonctionnalités de threading de haut niveau telles que les pools de threads ont été renvoyées dans un futur rapport technique C++ . Ils ne font pas partie de C++11, mais leur implémentation éventuelle devrait être entièrement construite sur les fonctionnalités de la bibliothèque de threads.

La nouvelle fonctionnalité std::asyncfournit une méthode pratique pour exécuter des tâches et les lier à un fichier std::future. L'utilisateur peut choisir si la tâche doit être exécutée de manière asynchrone sur un thread séparé ou de manière synchrone sur un thread qui attend la valeur. Par défaut, l'implémentation peut choisir, ce qui permet de profiter facilement de la concurrence matérielle sans surabonnement, et offre certains des avantages d'un pool de threads pour des utilisations simples.

Types de tuples

Les tuples sont des collections composées d'objets hétérogènes de dimensions pré-arrangées. Un tuple peut être considéré comme une généralisation des variables membres d'une structure.

La version C++11 du type de tuple TR1 a bénéficié de fonctionnalités C++11 telles que les modèles variadiques . Pour implémenter raisonnablement, la version TR1 nécessitait un nombre maximum de types contenus définis par l'implémentation et une astuce macro substantielle. En revanche, l'implémentation de la version C++11 ne nécessite aucun nombre maximal de types défini par l'implémentation explicite. Bien que les compilateurs aient une profondeur de récursivité maximale interne pour l'instanciation de modèle (ce qui est normal), la version C++11 des tuples n'exposera pas cette valeur à l'utilisateur.

En utilisant des modèles variadiques , la déclaration de la classe tuple ressemble à ceci :

template <class ...Types> class tuple;

Un exemple de définition et d'utilisation du type tuple :

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.

Il est possible de créer le tuple proofsans définir son contenu, mais seulement si les types des éléments du tuple possèdent des constructeurs par défaut. De plus, il est possible d'affecter un tuple à un autre tuple : si les types des deux tuples sont les mêmes, chaque type d'élément doit posséder un constructeur de copie ; sinon, chaque type d'élément du tuple de droite doit être convertible en celui du type d'élément correspondant du tuple de gauche ou que le type d'élément correspondant du tuple de gauche a un constructeur approprié.

typedef std::tuple <int , double, string       > tuple_1 t1;
typedef std::tuple <char, short , const char * > tuple_2 t2 ('X', 2, "Hola!");
t1 = t2; // Ok, first two elements can be converted,
         // the third one can be constructed from a 'const char *'.

Tout comme std::make_pairpour std::pair, il existe std::make_tuplepour créer automatiquement des std::tuples en utilisant la déduction de type et autoaide à déclarer un tel tuple. std::tiecrée des tuples de références lvalue pour aider à décompresser les tuples. std::ignoreaide aussi ici. Voir l'exemple :

auto record = std::make_tuple("Hari Ram", "New Delhi", 3.5, 'A');
std::string name ; float gpa ; char grade ;
std::tie(name, std::ignore, gpa, grade) = record ; // std::ignore helps drop the place name
std::cout << name << ' ' << gpa << ' ' << grade << std::endl ;

Des opérateurs relationnels sont disponibles (parmi des tuples avec le même nombre d'éléments), et deux expressions sont disponibles pour vérifier les caractéristiques d'un tuple (uniquement lors de la compilation) :

  • std::tuple_size<T>::valuerenvoie le nombre d'éléments dans le tuple T,
  • std::tuple_element<I, T>::typerenvoie le type du numéro d'objet Idu tuple T.

Tables de hachage

L'inclusion de tables de hachage (conteneurs associatifs non ordonnés) dans la bibliothèque standard C++ est l'une des demandes les plus récurrentes. Il n'a pas été adopté en C++03 en raison de contraintes de temps uniquement. Bien que les tables de hachage soient moins efficaces qu'un arbre équilibré dans le pire des cas (en présence de nombreuses collisions), elles fonctionnent mieux dans de nombreuses applications réelles.

Les collisions ne sont gérées que par chaînage linéaire car le comité n'a pas jugé opportun de standardiser les solutions d' adressage ouvert qui introduisent pas mal de problèmes intrinsèques (surtout lorsque l'effacement d'éléments est admis). Pour éviter les conflits de noms avec des bibliothèques non standard qui ont développé leurs propres implémentations de table de hachage, le préfixe « unordered » a été utilisé à la place de « hash ».

La nouvelle bibliothèque dispose de quatre types de tables de hachage, différenciées selon qu'elles acceptent ou non des éléments avec la même clé (clés uniques ou clés équivalentes) et si elles mappent chaque clé à une valeur associée. Ils correspondent aux quatre conteneurs associatifs existants basés sur un arbre de recherche binaire , avec un préfixe unordered_ .

Type de table de hachage Valeurs associées Clés équivalentes
std::unordered_set Non Non
std::unordered_multiset Non Oui
std::unordered_map Oui Non
std::unordered_multimap Oui Oui

Les nouvelles classes remplissent toutes les exigences d'une classe conteneur et disposent de toutes les méthodes nécessaires pour accéder aux éléments : insert, erase, begin, end.

Cette nouvelle fonctionnalité ne nécessitait aucune extension de base du langage C++ (bien que les implémentations tireront parti de diverses fonctionnalités du langage C++11), seule une petite extension de l'en-tête <functional>et l'introduction d'en-têtes <unordered_set>et de fichiers <unordered_map>. Aucune autre modification des classes standard existantes n'a été nécessaire et cela ne dépend d'aucune autre extension de la bibliothèque standard.

Expressions régulières

La nouvelle bibliothèque, définie dans le nouvel en-tête <regex>, est constituée de deux nouvelles classes :

  • les expressions régulières sont représentées par l'instance de la classe template std::regex;
  • les occurrences sont représentées par l'instance de la classe template std::match_results.

La fonction std::regex_searchest utilisée pour la recherche, tandis que pour « rechercher et remplacer », la fonction std::regex_replaceest utilisée qui renvoie une nouvelle chaîne. Les algorithmes std::regex_searchet std::regex_replaceprennent une expression régulière 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;:]"; // List of separator characters.

// this can be done using raw string literals:
// const char *reg_esp = R"([ ,.\t\n;:])";

std::regex rgx(reg_esp); // 'regex' is an instance of the template class
                         // 'basic_regex' with argument of type 'char'.
std::cmatch match; // 'cmatch' is an instance of the template class
                   // 'match_results' with argument of type 'const char *'.
const char *target = "Unseen University - Ankh-Morpork";

// Identifies all words of 'target' separated by characters of 'reg_esp'.
if (std::regex_search(target, match, rgx))
{
    // If words separated by specified characters are present.

    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 de doubles barres obliques inverses , car C++ utilise une barre oblique inverse comme caractère d'échappement. La fonctionnalité de chaîne brute C++11 pourrait être utilisée pour éviter le problème.

La bibliothèque <regex>ne nécessite ni modification d'un en-tête existant (bien qu'elle les utilise le cas échéant) ni une extension du langage de base. Dans POSIX C, les expressions régulières sont également disponibles dans la bibliothèque C POSIX#regex.h .

Pointeurs intelligents à usage général

C++11 fournit std::unique_ptr, et des améliorations vers std::shared_ptret std::weak_ptrdepuis TR1. std::auto_ptrest obsolète.

Facilité de nombres aléatoires extensible

La bibliothèque standard C offre la possibilité de générer des nombres pseudo-aléatoires via la fonction rand. Cependant, l'algorithme est entièrement délégué au fournisseur de la bibliothèque. C++ a hérité de cette fonctionnalité sans modification, mais C++11 fournit une nouvelle méthode pour générer des nombres pseudo-aléatoires.

La fonctionnalité de nombres aléatoires de C++11 est divisée en deux parties : un moteur de générateur qui contient l'état du générateur de nombres aléatoires et produit les nombres pseudo-aléatoires ; et une distribution, qui détermine l'étendue et la distribution mathématique du résultat. Ces deux éléments sont combinés pour former un objet générateur de nombres aléatoires.

Contrairement au standard C rand, le mécanisme C++11 sera livré avec trois algorithmes de moteur de générateur de base :

C++11 fournit également un certain nombre de distributions standard :

Le générateur et les distributions sont combinés comme dans cet exemple :

#include <random>
#include <functional>

std::uniform_int_distribution<int> distribution(0, 99);
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(distribution, engine);
int random = generator(); // Generate a uniform integral variate between 0 and 99.
int random2 = distribution(engine); // Generate another sample directly using the distribution and the engine objects.

Référence de l'emballage

Une référence wrapper est obtenue à partir d'une instance de la classe template reference_wrapper. Les références wrapper sont similaires aux références normales (' &') du langage C++. Pour obtenir une référence wrapper à partir de n'importe quel objet, le modèle de fonction refest utilisé (pour une référence constante crefest utilisé).

Les références de wrapper sont utiles avant tout pour les modèles de fonctions, où des références à des paramètres plutôt que des copies sont nécessaires :

// This function will obtain a reference to the parameter 'r' and increment it.
void func (int &r)  { r++; }

// Template function.
template<class F, class P> void g (F f, P t)  { f(t); }

int main()
{
    int i = 0;
    g (func, i); // 'g<void (int &r), int>' is instantiated
                 // then 'i' will not be modified.
    std::cout << i << std::endl; // Output -> 0

    g (func, std::ref(i)); // 'g<void(int &r),reference_wrapper<int>>' is instantiated
                           // then 'i' will be modified.
    std::cout << i << std::endl; // Output -> 1
}

Ce nouvel utilitaire a été ajouté à l'en- <utility>tête existant et n'a pas eu besoin d'extensions supplémentaires du langage C++.

Wrappers polymorphes pour les objets fonction

Les wrappers polymorphes pour les objets fonction sont similaires aux pointeurs de fonction en sémantique et en syntaxe, mais sont moins étroitement liés et peuvent faire référence sans distinction à tout ce qui peut être appelé (pointeurs de fonction, pointeurs de fonction membre ou foncteurs) dont les arguments sont compatibles avec ceux du wrapper .

Un exemple peut préciser ses caractéristiques :

std::function<int (int, int)> func; // Wrapper creation using
                                    // template class 'function'.
std::plus<int> add; // 'plus' is declared as 'template<class T> T plus( T, T ) ;'
                    // then 'add' is type 'int add( int x, int y )'.
func = add;  // OK - Parameters and return types are the same.

int a = func (1, 2); // NOTE: if the wrapper 'func' does not refer to any function,
                     // the exception 'std::bad_function_call' is thrown.

std::function<bool (short, short)> func2 ;
if (!func2)
{
    // True because 'func2' has not yet been assigned a function.

    bool adjacent(long x, long y);
    func2 = &adjacent; // OK - Parameters and return types are convertible.

    struct Test
    {
        bool operator()(short x, short y);
    };
    Test car;
    func = std::ref(car); // 'std::ref' is a template function that returns the wrapper
                          // of member function 'operator()' of struct 'car'.
}
func = func2; // OK - Parameters and return types are convertible.

La classe de modèle a functionété définie à l'intérieur de l'en-tête <functional>, sans qu'il soit nécessaire de modifier le langage C++.

Caractéristiques de type pour la métaprogrammation

La métaprogrammation consiste à créer un programme qui crée ou modifie un autre programme (ou lui-même). Cela peut se produire pendant la compilation ou pendant l'exécution. Le C++ Standards Committee a décidé d'introduire une bibliothèque qui permet la métaprogrammation lors de la compilation via des templates.

Voici un exemple de méta-programme, utilisant le standard C++03 : une récursivité d'instances de modèle pour le calcul d'exposants entiers :

template<int B, int N>
struct Pow
{
    // recursive call and recombination.
    enum{ value = B*Pow<B, N-1>::value };
};

template< int B >
struct Pow<B, 0>
{
    // ''N == 0'' condition of termination.
    enum{ value = 1 };
};
int quartic_of_three = Pow<3, 4>::value;

De nombreux algorithmes peuvent fonctionner sur différents types de données ; Les modèles de C++ prennent en charge la programmation générique et rendent le code plus compact et utile. Néanmoins, il est courant que les algorithmes aient besoin d'informations sur les types de données utilisés. Ces informations peuvent être extraites lors de l'instanciation d'une classe modèle à l'aide de traits de type .

Les traits de type peuvent identifier la catégorie d'un objet et toutes les caractéristiques d'une classe (ou d'une structure). Ils sont définis dans le nouvel en-tête <type_traits>.

Dans l'exemple suivant, il y a la fonction modèle 'elaborate' qui, selon les types de données donnés, instanciera l'un des deux algorithmes proposés ( algorithm.do_it).

// First way of operating.
template< bool B > struct Algorithm
{
    template<class T1, class T2> static int do_it (T1 &, T2 &)  { /*...*/ }
};

// Second way of operating.
template<> struct Algorithm<true>
{
    template<class T1, class T2> static int do_it (T1, T2)  { /*...*/ }
};

// Instantiating 'elaborate' will automatically instantiate the correct way to operate.
template<class T1, class T2>
int elaborate (T1 A, T2 B)
{
    // Use the second way only if 'T1' is an integer and if 'T2' is
    // in floating point, otherwise use the first way.
    return Algorithm<std::is_integral<T1>::value && std::is_floating_point<T2>::value>::do_it( A, B ) ;
}

Via les traits de type , définis dans l'en-tête <type_traits>, il est également possible de créer des opérations de transformation de type ( static_castet const_castsont insuffisantes à l'intérieur d'un modèle).

Ce type de programmation produit un code élégant et concis ; cependant le point faible de ces techniques est le débogage : inconfortable lors de la compilation et très difficile lors de l'exécution du programme.

Méthode uniforme pour calculer le type de retour des objets fonction

Déterminer le type de retour d'un objet de fonction modèle au moment de la compilation n'est pas intuitif, en particulier si la valeur de retour dépend des paramètres de la fonction. Par exemple:

struct Clear
{
    int    operator()(int) const;    // The parameter type is
    double operator()(double) const; // equal to the return type.
};

template <class Obj>
class Calculus
{
public:
    template<class Arg> Arg operator()(Arg& a) const
    {
        return member(a);
    }
private:
    Obj member;
};

En instanciant le modèle de classe Calculus<Clear>, l'objet fonction de calculusaura toujours le même type de retour que l'objet fonction de Clear. Cependant, classe donnée Confusedci - dessous:

struct Confused
{
    double operator()(int) const;     // The parameter type is not
    int    operator()(double) const;  // equal to the return type.
};

Si vous essayez d'instancier Calculus<Confused>, le type de retour de Calculusne sera pas le même que celui de la classe Confused. Le compilateur peut générer des avertissements sur la conversion de intvers doubleet vice versa.

TR1 introduit, et C++11 adopte, la classe modèle std::result_ofqui permet de déterminer et d'utiliser le type de retour d'un objet fonction pour chaque déclaration. L'objet CalculusVer2utilise l' std::result_ofobjet pour dériver le type de retour de l'objet fonction :

template< class Obj >
class CalculusVer2
{
public:
    template<class Arg>
    typename std::result_of<Obj(Arg)>::type operator()(Arg& a) const
    {
        return member(a);
    }
private:
    Obj member;
};

De cette façon, dans les instances d'objet de fonction, CalculusVer2<Confused>il n'y a pas de conversions, d'avertissements ou d'erreurs.

Le seul changement par rapport à la version TR1 de std::result_ofest que la version TR1 permettait à une implémentation de ne pas pouvoir déterminer le type de résultat d'un appel de fonction. En raison des modifications apportées à C++ pour la prise en charge de decltype, la version C++11 de std::result_ofn'a plus besoin de ces cas particuliers ; les implémentations sont nécessaires pour calculer un type dans tous les cas.

Compatibilité C améliorée

Pour la compatibilité avec C , à partir de C99, ceux-ci ont été ajoutés :

  • Préprocesseur :
    • macros variadiques ,
    • concaténation de chaînes littérales étroites/larges adjacentes,
    • _Pragma()– équivalent de #pragma.
  • long long – type entier d'une longueur d'au moins 64 bits.
  • __func__ – macro évaluant au nom de la fonction dans laquelle elle se trouve.
  • En-têtes :
    • cstdbool( stdbool.h),
    • cstdint( stdint.h),
    • cinttypes( inttypes.h).

Fonctionnalités initialement prévues mais supprimées ou non incluses

En direction d'un TR séparé :

  • Modules
  • Types décimaux
  • Fonctions spéciales mathématiques

Reporté:

  • notions
  • Prise en charge plus complète ou requise de la récupération de place
  • Réflexion
  • Portées de macro

Fonctionnalités supprimées ou obsolètes

Le terme point de séquence a été supprimé, étant remplacé en spécifiant que soit une opération est séquencée avant une autre, soit que deux opérations ne sont pas séquencées.

L'ancienne utilisation du mot-clé a exportété supprimée. Le mot-clé lui-même demeure, étant réservé pour une utilisation future potentielle.

Les spécifications d'exception dynamiques sont obsolètes. La spécification au moment de la compilation des fonctions de non-lancement d'exception est disponible avec le noexceptmot - clé, ce qui est utile pour l'optimisation.

std::auto_ptrest obsolète, ayant été remplacé par std::unique_ptr.

Les classes de base d'objet fonction ( std::unary_function, std::binary_function), les adaptateurs vers des pointeurs vers les fonctions et les adaptateurs vers les pointeurs vers les membres et les classes de classeur sont tous obsolètes.

Voir également

Les références

Liens externes