Avant C ++ 11, nous ne pouvions effectuer une initialisation en classe que sur des membres const statiques de type intégral ou énumération. Stroustrup en parle dans sa FAQ C ++ , en donnant l'exemple suivant:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
Et le raisonnement suivant:
Alors, pourquoi ces restrictions peu pratiques existent-elles? Une classe est généralement déclarée dans un fichier d'en-tête et un fichier d'en-tête est généralement inclus dans de nombreuses unités de traduction. Cependant, pour éviter les règles compliquées de l'éditeur de liens, C ++ exige que chaque objet ait une définition unique. Cette règle serait rompue si C ++ autorisait la définition en classe d'entités qui devaient être stockées en mémoire en tant qu'objets.
Cependant, C ++ 11 assouplit ces restrictions, permettant l'initialisation en classe des membres non statiques (§12.6.2 / 8):
Dans un constructeur non délégant, si un membre de données non statique donné ou une classe de base n'est pas désigné par un mem-initializer-id (y compris le cas où il n'y a pas de mem-initializer-list car le constructeur n'a pas d' initialiseur ctor ) et l'entité n'est pas une classe de base virtuelle d'une classe abstraite (10.4), alors
- si l'entité est un membre de données non statique qui a un initialiseur d'accolade ou d'égalité , l'entité est initialisée comme spécifié en 8.5;
- sinon, si l'entité est un membre variant (9.5), aucune initialisation n'est effectuée;
- sinon, l'entité est initialisée par défaut (8.5).
La section 9.4.2 permet également l'initialisation en classe des membres statiques non const s'ils sont marqués avec le constexpr
spécificateur.
Alors, qu'est-il arrivé aux raisons des restrictions que nous avions dans C ++ 03? Acceptons-nous simplement les "règles compliquées de l'éditeur de liens" ou y a-t-il autre chose qui a changé qui facilite la mise en œuvre?
la source
Réponses:
La réponse courte est qu'ils ont gardé l'éditeur de liens à peu près le même, au détriment de rendre le compilateur encore plus compliqué qu'auparavant.
C'est-à-dire qu'au lieu que cela entraîne plusieurs définitions à trier par l'éditeur de liens, il n'en résulte toujours qu'une seule définition et le compilateur doit le trier.
Cela conduit également à des règles un peu plus complexes que le programmeur doit également garder triées, mais c'est assez simple pour que ce ne soit pas un gros problème. Les règles supplémentaires interviennent lorsque vous avez deux initialiseurs différents spécifiés pour un seul membre:
class X { int a = 1234; public: X() = default; X(int z) : a(z) {} };
Maintenant, les règles supplémentaires à ce stade traitent de la valeur utilisée pour initialiser
a
lorsque vous utilisez le constructeur non par défaut. La réponse à cela est assez simple: si vous utilisez un constructeur qui ne spécifie aucune autre valeur, alors le1234
serait utilisé pour initialisera
- mais si vous utilisez un constructeur qui spécifie une autre valeur, alors le1234
est fondamentalement ignoré.Par exemple:
#include <iostream> class X { int a = 1234; public: X() = default; X(int z) : a(z) {} friend std::ostream &operator<<(std::ostream &os, X const &x) { return os << x.a; } }; int main() { X x; X y{5678}; std::cout << x << "\n" << y; return 0; }
Résultat:
1234 5678
la source
Je suppose que ce raisonnement aurait pu être rédigé avant la finalisation des modèles. Après toutes les «règles compliquées de l'éditeur de liens» nécessaires pour les initialiseurs en classe des membres statiques étaient / étaient déjà nécessaires pour que C ++ 11 prenne en charge les membres statiques des modèles.
Considérer
struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed, // thanks @Kapil for pointing that out // vs. template <class T> struct B { static int s; } template <class T> int B<T>::s = ::ComputeSomething(); // or template <class T> void Foo() { static int s = ::ComputeSomething(); s++; std::cout << s << "\n"; }
Le problème pour le compilateur est le même dans les trois cas: dans quelle unité de traduction doit-il émettre la définition
s
et le code nécessaire pour l'initialiser? La solution simple est de l'émettre partout et de laisser l'éditeur de liens le trier. C'est pourquoi les éditeurs de liens supportaient déjà des choses comme__declspec(selectany)
. Il n'aurait tout simplement pas été possible d'implémenter C ++ 03 sans lui. Et c'est pourquoi il n'était pas nécessaire d'étendre l'éditeur de liens.Pour le dire plus franchement: je pense que le raisonnement donné dans l'ancienne norme est tout simplement faux.
MISE À JOUR
Comme l'a souligné Kapil, mon premier exemple n'est même pas autorisé dans la norme actuelle (C ++ 14). Je l'ai laissé de toute façon, car il IMO est le cas le plus difficile pour l'implémentation (compilateur, éditeur de liens). Mon point est: même ce cas n'est pas plus difficile que ce qui est déjà autorisé, par exemple lors de l'utilisation de modèles.
la source
En théorie, la
So why do these inconvenient restrictions exist?...
raison est valable mais elle peut être facilement contournée et c'est exactement ce que fait C ++ 11.Lorsque vous incluez un fichier, il inclut simplement le fichier et ignore toute initialisation. Les membres sont initialisés uniquement lorsque vous instanciez la classe.
En d'autres termes, l'initialisation est toujours liée au constructeur, seule la notation est différente et est plus pratique. Si le constructeur n'est pas appelé, les valeurs ne sont pas initialisées.
Si le constructeur est appelé, les valeurs sont initialisées avec une initialisation en classe si elle est présente ou le constructeur peut remplacer cela avec sa propre initialisation. Le chemin d'initialisation est essentiellement le même, c'est-à-dire via le constructeur.
Cela ressort clairement de la propre FAQ de Stroustrup sur C ++ 11.
la source
Y::c3
dans la question? Si je comprends bien,c3
sera toujours initialisé sauf s'il existe un constructeur qui remplace la valeur par défaut donnée dans la déclaration.