web-dev-qa-db-fra.com

Pourquoi ne puis-je pas initialiser un membre statique ou un tableau statique non-const dans la classe?

Pourquoi ne puis-je pas initialiser un tableau non-const static ou static dans une classe?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

le compilateur pose les erreurs suivantes:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member ‘b’
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type ‘const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type ‘int [2]’

J'ai deux questions:

  1. Pourquoi ne puis-je pas initialiser static membres de la classe?
  2. Pourquoi ne puis-je pas initialiser les tableaux static en classe, même le tableau const?
91
Yishu Fang

Pourquoi je ne peux pas initialiser static membres de la classe?

La norme C++ ne permet d'initialiser que les types d'intégrale ou d'énumération à constante constante dans la classe. C'est la raison pour laquelle a est autorisé à être initialisé alors que d'autres ne le sont pas.

Référence:
C++ 03 9.4.2 Membres de données statiques
§4

Si un membre de données statique est de type énum intégral ou const constant, sa déclaration dans la définition de classe peut spécifier un initialisateur de constante qui doit être une expression constante intégrale (5.19). Dans ce cas, le membre peut apparaître dans des expressions constantes intégrales. Le membre doit toujours être défini dans une portée d'espace de noms s'il est utilisé dans le programme et la définition de la portée d'espace de noms ne doit pas contenir d'initialiseur.

Que sont les types intégraux?

C++ 03 3.9.1 Types fondamentaux
§sept

Les types bool, char, wchar_t et les types entiers signés et non signés sont collectivement appelés types intégraux.43) Un synonyme pour type intégral est le type entier.

Note de bas de page:

43) Par conséquent, les énumérations (7.2) ne font pas partie intégrante; cependant, les énumérations peuvent être promues en int, unsigned int, long ou unsigned long, comme spécifié en 4.5.

Solution de contournement:

Vous pouvez utiliser l'astuce enum pour initialiser un tableau dans la définition de votre classe.

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

Pourquoi la norme ne le permet-elle pas?

Bjarne explique cela ici:

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.

Pourquoi sont seulement static const types et énumérations entiers autorisés Initialisation en classe?

La réponse est cachée dans la citation de Bjarne, lisez-la attentivement,
"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."

Notez que seulement static const les entiers peuvent être traités comme des constantes de temps de compilation. Le compilateur sait que la valeur entière ne changera pas à tout moment et il peut donc appliquer sa propre magie et appliquer des optimisations. Le compilateur insère simplement ces membres de classe, c.-à-d. Qu'ils ne sont plus stockés en mémoire. , il donne à ces variables l'exception à la règle mentionnée par Bjarne.

Il convient de noter ici que même si static const Les valeurs intégrales peuvent avoir une initialisation en classe, la prise de l'adresse de telles variables n'est pas autorisée. On peut prendre l'adresse d'un membre statique si (et seulement si) il a une définition hors classe. Ceci valide encore le raisonnement ci-dessus.

les énumérations sont autorisées car les valeurs d'un type énuméré peuvent être utilisées là où les ints sont attendus.voir la citation ci-dessus


Comment cela change-t-il en C++ 11?

C++ 11 assouplit la restriction dans une certaine mesure.

C++ 11 9.4.2 Membres de données statiques
§3

Si un membre de données statique est de type littéral const, sa déclaration dans la définition de classe peut spécifier un accolade-ou-égal-initialiseur dans lequel chaque initializer-clause = c'est un Affectation-expression est une expression constante. Un membre de données statique de type littéral peut être déclaré dans la définition de classe avec le constexpr specifier; si oui, sa déclaration doit spécifier un accolade ou égal initialisateur dans lequel chaque initializer-clause qui est un affectation- expression est une expression constante. [Remarque: dans les deux cas, le membre peut apparaître dans des expressions constantes. —End note] Le membre doit toujours être défini dans une étendue d'espace de nommage s'il est utilisé dans le programme et la définition de l'étendue d'espace de nommage ne doit pas contenir d'initialiseur.

De plus, C++ 11 will permet (§12.6.2.8) d'initialiser un membre de données non statique à l'endroit où il est déclaré (dans sa classe). Cela signifiera beaucoup de sémantique utilisateur facile.

Notez que ces fonctionnalités n’ont pas encore été implémentées dans la dernière version de gcc 4.7. Il est donc possible que vous rencontriez toujours des erreurs de compilation.

125
Alok Save

Cela semble être un vestige de l’époque des simples linkers. Vous pouvez utiliser des variables statiques dans les méthodes statiques comme solution de contournement:

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

et

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

et

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

construire:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

courir:

./main

Le fait que cela fonctionne (systématiquement, même si la définition de classe est incluse dans différentes unités de compilation), montre que l'éditeur de liens aujourd'hui (gcc 4.9.2) est en fait assez intelligent.

Funny: Prints 0123 sur le bras et 3210 sur x86.

3
not-a-user

Je pense que c'est pour vous empêcher de mélanger des déclarations et des définitions. (Réfléchissez aux problèmes qui pourraient survenir si vous incluez le fichier à plusieurs endroits.)

1
Mehrdad