93 votes

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

Avant C++11, nous ne pouvions effectuer une initialisation en classe que sur les membres statiques const 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 gênantes 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 des règles de liaison compliquées, le C++ exige que chaque objet ait une définition unique. Cette règle serait brisée si le C++ autorisait la définition en classe d'entités devant être stockées en mémoire en tant qu'objets.

Toutefois, C++11 assouplit ces restrictions, en autorisant l'initialisation en classe des membres non statiques (§12.6.2/8) :

Dans un constructeur non-délégataire, si un membre de données non statique donné ou une classe de base n'est pas désigné par une balise mem-initializer-id (y compris le cas où il n'y a pas de mem-initializer-list car le constructeur n'a pas de ctor-initialisateur ) 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 initialisateur d'accolade ou d'égalité l'entité est initialisée comme indiqué au point 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 par l'attribut constexpr spécificateur.

Alors, qu'est-il advenu des raisons des restrictions que nous avions en C++03 ? Devons-nous simplement accepter les "règles compliquées de l'éditeur de liens" ou quelque chose d'autre a-t-il changé qui rende cela plus facile à mettre en œuvre ?

70voto

Jerry Coffin Points 237758

La réponse courte est qu'ils ont gardé le linker à peu près le même, au prix de rendre le compilateur encore plus compliqué qu'auparavant.

C'est à dire qu'au lieu que cela résulte en de multiples définitions à trier par l'éditeur de liens, il n'y a toujours qu'une seule définition, et le compilateur doit la trier.

Cela conduit également à des règles un peu plus complexes pour l'élément programmeur à trier, mais c'est assez simple pour ne pas être un gros problème. Les règles supplémentaires interviennent lorsque deux initialisateurs différents sont 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 à cette question est assez simple : si vous utilisez un constructeur qui ne spécifie pas d'autre valeur, alors la méthode d'évaluation de l'impact sur l'environnement est utilisée. 1234 serait utilisé pour initialiser a -- mais si vous utilisez un constructeur qui spécifie une autre valeur, alors l'option 1234 est fondamentalement ignorée.

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

10voto

Paul Groke Points 2799

Je suppose que ce raisonnement a pu être écrit avant que les modèles ne soient finalisés. Après tout, les "règles de liaison compliquées" nécessaires pour les initialisateurs en classe des membres statiques étaient déjà nécessaires pour que C++11 supporte les membres statiques des modèles.

Pensez à

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 de s et le code nécessaire pour l'initialiser ? La solution simple est de l'émettre partout et de laisser le linker faire le tri. C'est pourquoi les linkers supportent déjà des choses comme __declspec(selectany) . Il n'aurait pas été possible d'implémenter C++03 sans elle. Et c'est pourquoi il n'était pas nécessaire d'étendre le linker.

Pour le dire plus crûment : Je pense que le raisonnement donné dans l'ancienne norme est tout simplement faux.


UPDATE

Comme l'a souligné Kapil, mon premier exemple n'est même pas autorisé dans la norme actuelle (C++14). Je l'ai quand même laissé, parce que c'est le cas le plus difficile pour l'implémentation (compilateur, linker). Ce que je veux dire, c'est que même que n'est pas plus difficile que ce qui est déjà autorisé, par exemple lors de l'utilisation de modèles.

9voto

zadane Points 934

En théorie So why do these inconvenient restrictions exist?... La raison est valable mais elle peut être facilement contournée et c'est exactement ce que fait C++ 11.

Quand vous inclure un fichier, il inclut simplement le fichier et ne tient pas compte de l'initialisation. Les membres ne sont initialisés que lorsque vous instancier la classe.

En d'autres termes, l'initialisation est toujours liée au constructeur, mais la notation est différente et 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 l'initialisation de la classe si elle est présente ou le constructeur peut la remplacer par sa propre initialisation. Le chemin de l'initialisation est essentiellement le même, c'est-à-dire via le constructeur.

C'est ce qui ressort de l'étude de Stroustrup FAQ sur C++ 11.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X