web-dev-qa-db-fra.com

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

Avant C++ 11, nous ne pouvions effectuer l'initialisation en classe que sur les membres const statiques de type intégrale 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. Toutefois, pour éviter les règles compliquées de l'éditeur de liens, C++ requiert que chaque objet ait une définition unique. Cette règle serait rompue si C++ permettait la définition en classe des entités devant être stockées en mémoire en tant qu'objets.

Cependant, C++ 11 assouplit ces restrictions, permettant ainsi l'initialisation en classe de membres non statiques (§12.6.2/8):

Dans un constructeur non délégué, si un membre de données non statique ou une classe de base donnée n'est pas désigné par un mem-initializer-id (avec le cas où il n'y a pas mem-initializer-list car le constructeur n'a pas ctor-initializer ) et que 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 doté d'un accolade-ou-égal-initialiseur , 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 autorise également l'initialisation en classe des membres statiques non const s'ils sont marqués avec le spécificateur constexpr.

Alors qu'est-il arrivé aux raisons des restrictions que nous avions en C++ 03? Est-ce que nous acceptons simplement les "règles compliquées de l'éditeur de liens" ou si quelque chose d'autre a changé pour faciliter la mise en œuvre?

79
Joseph Mansfield

La réponse courte est qu’ils ont gardé l’éditeur de liens à peu près identique, au prix de compliquer encore plus le compilateur.

En d'autres termes, au lieu de générer plusieurs définitions pour le lieur, le résultat est toujours une seule définition, et le compilateur doit le résoudre.

Cela conduit également à des règles un peu plus complexes pour le programmeur à conserver, mais il est généralement assez simple pour que ce ne soit pas un gros problème. Les règles supplémentaires entrent en vigueur 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) {}
};

À présent, les règles supplémentaires traitent de la valeur utilisée pour initialiser a lorsque vous utilisez le constructeur autre que celui par défaut. La réponse à cette question est assez simple: si vous utilisez un constructeur qui ne spécifie aucune autre valeur, alors le 1234 serait utilisé pour initialiser a - mais si vous utilisez un constructeur qui spécifie une autre valeur, alors le 1234 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
62
Jerry Coffin

Je suppose que ce raisonnement a peut-être été écrit avant la finalisation des modèles. Après que toutes les "règles de l'éditeur de liens complexes" nécessaires aux initialiseurs en classe des membres statiques é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 du 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 l’éditeur de liens le résoudre. C'est pourquoi les lieurs supportaient déjà des choses comme __declspec(selectany). Il n'aurait simplement pas été possible d'implémenter C++ 03 sans cela. 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 le standard actuel (C++ 14). Je l’ai laissé de toute façon, car c’est le cas le plus difficile pour l’implémentation (compilateur, éditeur de liens). Mon point est: même que le cas n’est pas plus difficile que ce qui est déjà autorisé, par exemple. lors de l'utilisation de modèles.

8
Paul Groke

En théorie So why do these inconvenient restrictions exist?... _ raison est valable mais 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 ne sont initialisés que lorsque vous instanciez 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 une initialisation dans la classe, le cas échéant, ou le constructeur peut les remplacer par une propre initialisation. Le chemin d’initialisation est essentiellement le même, c’est-à-dire via le constructeur.

Cela est évident dans Stroustrup own FAQ sur C++ 11.

6
zar