C ++ 11 permet l'initialisation en classe des membres non statiques et non const. Qu'est ce qui a changé?


87

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 constexprspé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?


5
Rien ne s'est passé. Les compilateurs sont devenus plus intelligents avec tous ces modèles d'en-tête uniquement, c'est donc une extension relativement facile maintenant.
Öö Tiib

Assez intéressant sur mon IDE lorsque je sélectionne la compilation pré C ++ 11, je suis autorisé à initialiser des membres intégraux const non statiques
Dean P

Réponses:


67

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 alorsque 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 le 1234serait utilisé pour initialiser a- mais si vous utilisez un constructeur qui spécifie une autre valeur, alors le 1234est 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

1
On dirait que c'était tout à fait possible avant. Cela a simplement rendu le travail d'écriture d'un compilateur plus difficile. Est-ce une déclaration juste?
allyourcode

10
@allyourcode: Oui et non. Oui, cela a rendu l'écriture du compilateur plus difficile. Mais non, car cela a également rendu l'écriture de la spécification C ++ un peu plus difficile.
Jerry Coffin

Y a-t-il une différence comment initialiser le membre de classe: int x = 7; ou int x {7} ;?
mbaros

9

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 set 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.


Dommage que cela n'ait pas obtenu de votes positifs, car de nombreuses fonctionnalités de C ++ 11 sont similaires en ce que les compilateurs incluaient déjà les capacités ou les optimisations nécessaires.
Alex Court le

@AlexCourt J'ai écrit cette réponse récemment. La question et la réponse de Jerry datent de 2012. Donc je suppose que c'est pourquoi ma réponse n'a pas reçu beaucoup d'attention.
Paul Groke

1
Cela ne sera pas conforme à "struct A {static int s = :: ComputeSomething ();}" car seule la const statique peut être initialisée en classe
PapaDiHatti

8

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.


Re "Si le constructeur n'est pas appelé, les valeurs ne sont pas initialisées": Comment pourrais-je contourner l'initialisation du membre Y::c3dans la question? Si je comprends bien, c3sera toujours initialisé sauf s'il existe un constructeur qui remplace la valeur par défaut donnée dans la déclaration.
Peter - Réintègre Monica le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.