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

116

Pourquoi ne puis-je pas initialiser un staticmembre ou un statictableau non const 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 émet 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 staticles membres de données en classe?
  2. Pourquoi ne puis-je pas initialiser les statictableaux en classe, même le consttableau?
Yishu Fang
la source
1
Je pense que la raison principale est qu'il est difficile de bien faire. En principe, vous pourriez probablement faire ce dont vous parlez, mais il y aurait des effets secondaires étranges. Comme si votre exemple de tableau était autorisé, alors vous pourrez peut-être obtenir la valeur de A :: c [0], mais pas pouvoir passer A :: c à une fonction car cela nécessiterait une adresse et une compilation les constantes n'ont pas d'adresse. C ++ 11 a permis une partie de cela en utilisant constexpr.
Vaughn Cato
Grande question et réponse makred. Lien qui m'a aidé: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Réponses:

144

Pourquoi je ne peux pas initialiser staticles membres de données en classe?

La norme C ++ autorise uniquement l'initialisation de types intégraux ou énumération constants statiques à l'intérieur de la classe. C'est la raison apour laquelle il est permis d'ê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 const intégrale ou énumération const, sa déclaration dans la définition de classe peut spécifier un initialiseur 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 portée d'espace de noms ne doit pas contenir d'initialiseur.

Quels sont les types intégraux?

C ++ 03 3.9.1 Types fondamentaux
§7

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 de type intégral est le type entier.

Note de bas de page:

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

Solution de contournement:

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

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

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

};

Pourquoi la norme ne permet-elle pas cela?

Bjarne l'explique bien 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. 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.

Pourquoi seuls static constles types intégraux et les énumérations sont-ils autorisés dans l'initialisation en classe?

La réponse est cachée dans la citation de Bjarne, lisez-la attentivement,
"C ++ exige que chaque objet ait une définition unique. Cette règle serait enfreinte si C ++ autorisait la définition en classe d'entités qui devaient être stockées en mémoire en tant qu'objets."

Notez que seuls les static constentiers 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 peut donc appliquer sa propre magie et appliquer des optimisations, le compilateur intègre simplement ces membres de classe, c'est-à-dire qu'ils ne sont plus stockés en mémoire, car le besoin d'être stocké en mémoire est supprimé , il donne à ces variables l'exception à la règle mentionnée par Bjarne.

Il est à noter ici que même si static constles valeurs intégrales peuvent avoir une initialisation en classe, la prise d'adresse de ces 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, ce qui 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ù des entiers sont attendus. voir la citation ci-dessus


Comment cela change-t-il dans 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 initialiseur d'accolade ou d'égalité dans lequel chaque clause d'initialisation qui est une expression d'affectation 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 initialiseur d'accolade ou d'égalité dans lequel chaque clause d'initialisation qui est une expression d'affectationest une expression constante. [Remarque: dans ces deux cas, le membre peut apparaître dans des expressions constantes. —End note] 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 portée d'espace de noms ne doit pas contenir d'initialiseur.

En outre, C ++ 11 va permettre (§12.6.2.8) un élément de données non-statique pour être initialisé où il est déclaré (dans sa classe). Cela signifiera une sémantique utilisateur beaucoup plus simple.

Notez que ces fonctionnalités n'ont pas encore été implémentées dans la dernière version de gcc 4.7, vous risquez donc toujours d'avoir des erreurs de compilation.

Alok Save
la source
7
Les choses sont différentes en C ++ 11. La réponse pourrait utiliser la mise à jour.
bames53
4
Cela ne semble pas être vrai: "Notez que seuls les entiers statiques const 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 peut donc appliquer sa propre magie et appliquer des optimisations, le compilateur simplement intègre de tels membres de classe, c'est-à-dire qu'ils ne sont plus stockés en mémoire , " Êtes-vous sûr qu'ils ne sont pas nécessairement stockés en mémoire? Et si je fournis des définitions pour les membres? Qu'est-ce qui &memberreviendrait?
Nawaz
2
@Als: Ouais. Voilà ma question. Alors, pourquoi C ++ autorise-t-il l'initialisation en classe uniquement pour les types intégraux, votre réponse ne répond pas correctement. Pensez à pourquoi il ne permet pas l'initialisation pour le static const char*membre?
Nawaz
3
@Nawaz: Étant donné que C ++ 03 n'autorisait l' initialisation de constante que pour le type d'énumération statique et const intégral et const et aucun autre type, C ++ 11 étend cela à un type littéral const qui assouplit les normes pour l'initialisation en classe. en C ++ 03 était peut-être un oubli qui justifiait un changement et a donc été corrigé en C ++ 11, s'il y a des raisons tactiques traditionnelles pour le changement, je n'en suis pas au courant.Si vous en êtes au courant, n'hésitez pas à partager leur.
Alok Save
4
Le par "Contournement" que vous avez mentionné ne fonctionne pas avec g ++.
iammilind
4

Cela semble une relique de l'ancien temps des simples linkers. Vous pouvez utiliser des variables statiques dans des 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 (de manière cohérente, 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.

Drôle: impressions 0123sur le bras et 3210sur x86.

pas un utilisateur
la source
1

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

user541686
la source
0

C'est parce qu'il ne peut y avoir qu'une seule définition de A::acelle que toutes les unités de traduction utilisent.

Si vous avez joué static int a = 3;dans une classe dans un en-tête inclus dans toutes les unités de traduction, vous obtiendrez plusieurs définitions. Par conséquent, la définition non hors ligne d'un statique est forcée à générer une erreur de compilation.

Utiliser static inlineou static constremédier à cela. static inlineNe concrétise le symbole que s'il est utilisé dans l'unité de traduction et garantit que l'éditeur de liens sélectionne et ne laisse qu'une copie s'il est défini dans plusieurs unités de traduction en raison de son appartenance à un groupe comdat. constat file scope fait que le compilateur n'émet jamais de symbole car il est toujours immédiatement substitué dans le code sauf s'il externest utilisé, ce qui n'est pas autorisé dans une classe.

Une chose à noter est qu'elle static inline int b;est traitée comme une définition alors que static const int bou static const A b;est toujours traitée comme une déclaration et doit être définie hors ligne si vous ne la définissez pas à l'intérieur de la classe. Fait intéressant, il static constexpr A b;est traité comme une définition, alors que static constexpr int b;c'est une erreur et doit avoir un initialiseur (c'est parce qu'ils deviennent maintenant des définitions et comme toute définition const / constexpr à la portée du fichier, ils nécessitent un initialiseur qu'un int n'a pas mais un type de classe car il a un implicite = A()lorsqu'il s'agit d'une définition - clang le permet mais gcc vous oblige à initialiser explicitement ou c'est une erreur. Ce n'est pas un problème avec inline à la place). static const A b = A();n'est pas autorisé et doit être constexprouinlineafin de permettre à un initialiseur pour un objet statique de type classe, c'est-à-dire de faire un membre statique de type classe plus qu'une déclaration. Donc, oui dans certaines situations, ce A a;n'est pas la même chose que l'initialisation explicite A a = A();(la première peut être une déclaration mais si seule une déclaration est autorisée pour ce type, la seconde est une erreur. Cette dernière ne peut être utilisée que sur une définition. En constexprfait une définition ). Si vous utilisez constexpret spécifiez un constructeur par défaut, le constructeur devra êtreconstexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

Un membre statique est une déclaration de portée de fichier pure et simple extern int A::a;(qui ne peut être faite que dans la classe et les définitions hors ligne doivent faire référence à un membre statique d'une classe et doivent être des définitions et ne peuvent pas contenir d'extern) alors qu'un membre non statique fait partie de la définition de type complète d'une classe et ont les mêmes règles que les déclarations de portée de fichier sans extern. Ce sont implicitement des définitions. Il en int i[]; int i[5];va de même pour une redéfinition alors static int i[]; int A::i[5];que contrairement à 2 externs, le compilateur détectera toujours un membre en double si vous le faites static int i[]; static int i[5];dans la classe.

Lewis Kelsey
la source
-3

les variables statiques sont spécifiques à une classe. Les constructeurs initialisent les attributs ESPECIALY pour une instance.


la source