Comment déclarer un const statique * dans votre fichier d’en-tête?

J’aimerais définir un caractère constant * dans mon fichier d’en-tête pour mon fichier .cpp à utiliser. J’ai donc essayé ceci:

private: static const char *SOMETHING = "sumthing"; 

Ce qui m’amène à l’erreur de compilation suivante:

erreur C2864: ‘SomeClass :: SOMETHING’: seuls les membres constants statiques des données peuvent être initialisés dans une classe

Je suis nouveau en C ++. Qu’est-ce qui se passe ici? Pourquoi est-ce illégal? Et comment pouvez-vous le faire alternativement?

Vous devez définir des variables statiques dans une unité de traduction, sauf si elles sont de type intégral.

Dans votre entête:

 private: static const char *SOMETHING; static const int MyInt = 8; // would be ok 

Dans le fichier .cpp:

 const char *YourClass::SOMETHING = "something"; 

Norme C ++, 9.4.2 / 4:

Si un membre de données statique est de type const integr ou const enumeration, sa déclaration dans la définition de classe peut spécifier un initialiseur constant qui doit être une expression constante intégrale. Dans ce cas, le membre peut apparaître dans des expressions constantes intégrales dans sa scope. Le membre doit toujours être défini dans une scope d’espace de nommage s’il est utilisé dans le programme et la définition de la scope de l’espace de noms ne doit pas contenir d’initialiseur.

Pour répondre à la question du PO sur la raison pour laquelle il est uniquement autorisé avec les types intégraux.

Lorsqu’un object est utilisé en tant que lvalue (c’est-à-dire en tant qu’adresse stockée), il doit satisfaire à la “règle de définition unique” (ODR), c.-à-d. Le compilateur ne peut pas et ne veut pas décider quelle unité de traduction doit définir cet object. C’est votre responsabilité. En définissant cet object quelque part que vous ne le définissez pas, vous indiquez au compilateur que vous souhaitez le définir ici , dans cette unité de traduction spécifique.

Pendant ce temps, les constantes intégrales du langage C ++ ont un statut spécial. Ils peuvent former des expressions constantes intégrales (ICE). Dans ICEs, les constantes intégrales sont utilisées comme valeurs ordinaires et non comme des objects (c’est-à-dire qu’il n’est pas pertinent que cette valeur intégrale ait ou non l’adresse dans le stockage). En fait, les ICE sont évalués au moment de la compilation. Afin de faciliter une telle utilisation des constantes intégrales, leurs valeurs doivent être visibles globalement. Et la constante elle-même n’a pas vraiment besoin d’une place réelle dans le stockage. A cause de cela, les constantes intégrales ont reçu un traitement spécial: elles étaient autorisées à inclure leurs initialiseurs dans le fichier d’en-tête, et l’obligation de fournir une définition était assouplie (de facto, puis de jure).

D’autres types constants n’ont pas de telles propriétés. Les autres types de constantes sont pratiquement toujours utilisés comme des lvalues ​​(ou du moins ne peuvent pas participer aux ICE ou à quelque chose de similaire à l’ICE), ce qui signifie qu’elles nécessitent une définition. Le rest suit.

L’erreur est que vous ne pouvez pas initialiser un static const char* dans la classe. Vous ne pouvez y initialiser que des variables entières.

Vous devez déclarer la variable membre dans la classe, puis l’initialiser en dehors de la classe:

// header de fichier

 class Foo { static const char *SOMETHING; // rest of class }; 

// fichier cpp

 const char *Foo::SOMETHING = "sumthing"; 

Si cela vous semble ennuyeux, pensez-y car l’initialisation ne peut apparaître que dans une unité de traduction. Si c’était dans la définition de la classe, cela serait généralement inclus par plusieurs fichiers. Les entiers constants sont un cas particulier (ce qui signifie que le message d’erreur n’est peut-être pas aussi clair qu’il pourrait l’être), et les compilateurs peuvent remplacer efficacement les utilisations de la variable par la valeur entière.

En revanche, une variable char* pointe sur un object réel en mémoire, ce qui est nécessaire pour exister réellement, et c’est la définition (y compris l’initialisation) qui fait que l’object existe. La “règle de définition unique” signifie que vous ne voulez donc pas le mettre dans un en-tête, car alors toutes les unités de traduction, y compris cet en-tête, contiendraient la définition. Ils ne peuvent pas être liés, même si la chaîne contient les mêmes caractères, car avec les règles C ++ actuelles, vous avez défini deux objects différents portant le même nom, ce qui n’est pas légal. Le fait qu’ils aient les mêmes caractères ne rend pas les choses légales.

 class A{ public: static const char* SOMETHING() { return "something"; } }; 

Je le fais tout le temps, en particulier pour les parameters constants coûteux.

 class A{ static const expensive_to_construct& default_expensive_to_construct(){ static const expensive_to_construct xp2c(whatever is needed); return xp2c; } }; 

Avec C ++ 11, vous pouvez utiliser le mot-clé constexpr et écrire dans votre en-tête:

 private: static constexpr const char* SOMETHING = "something"; 

Remarques:

  • constexpr fait de constexpr un pointeur constant pour que vous ne puissiez pas écrire

     SOMETHING = "something different"; 

    plus tard.

  • Selon votre compilateur, vous devrez peut-être également écrire une définition explicite dans le fichier .cpp:

     constexpr const char* MyClass::SOMETHING; 

Si vous utilisez Visual C ++, vous pouvez le faire de manière non portable à l’aide d’indicateurs dans l’éditeur de liens …

 // In foo.h... class Foo { public: static const char *Bar; }; // Still in foo.h; doesn't need to be in a .cpp file... __declspec(selectany) const char *Foo::Bar = "Blah"; 

__declspec(selectany) signifie que même si Foo::Bar sera déclaré dans plusieurs fichiers objects, l’éditeur de liens n’en prendra qu’un.

Gardez à l’esprit que cela ne fonctionnera qu’avec la chaîne d’outils Microsoft. Ne vous attendez pas à ce que ce soit portable.

Il existe une astuce que vous pouvez utiliser avec des modèles pour fournir des constantes contenant uniquement des fichiers H.

(note, ceci est un exemple moche, mais fonctionne textuellement au moins en g ++ 4.6.1.)

(fichier values.hpp)

 #include  template class tValues { public: static const char* myValue; }; template  const char* tValues::myValue = "This is a value"; typedef tValues<0> Values; std::ssortingng otherCompUnit(); // test from other compilation unit 

(main.cpp)

 #include  #include "values.hpp" int main() { std::cout << "from main: " << Values::myValue << std::endl; std::cout << "from other: " << otherCompUnit() << std::endl; } 

(other.cpp)

 #include "values.hpp" std::ssortingng otherCompUnit () { return std::ssortingng(Values::myValue); } 

Comstackr (par exemple, g ++ -o main main.cpp other.cpp && ./main) et voir deux unités de compilation référençant la même constante déclarée dans un en-tête:

 from main: This is a value from other: This is a value 

Dans MSVC, vous pouvez plutôt utiliser __declspec (selectany)

Par exemple:

 __declspec(selectany) const char* data = "My data"; 

Initialiseur constant autorisé par C ++ Standard uniquement pour les types intégrale ou énumération. Voir 9.4.2 / 4 pour plus de détails:

Si un membre de données statique est de type const integr ou const enumeration, sa déclaration dans la définition de classe peut spécifier un initialiseur constant 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 scope d’espace de nom s’il est utilisé dans le programme et la définition de scope d’espace de noms ne doit pas contenir d’initialiseur.

Et 9.4.2 / 7:

Les membres de données statiques sont initialisés et détruits exactement comme des objects non locaux (3.6.2, 3.6.3).

Donc, vous devriez écrire quelque part dans le fichier cpp:

 const char* SomeClass::SOMETHING = "sumthing"; 

Pour répondre à la question pourquoi , les types intégraux sont spéciaux en ce sens qu’ils ne font pas référence à un object alloué mais plutôt à des valeurs qui sont dupliquées et copiées. C’est juste une décision d’implémentation prise lorsque le langage a été défini, qui consistait à gérer les valeurs en dehors du système d’object et de la rendre aussi efficace que possible.

Cela n’explique pas exactement pourquoi ils sont autorisés en tant qu’initialiseurs dans un type, mais considérez-le essentiellement comme un #define et cela fera alors partie du type et non de l’object.