web-dev-qa-db-fra.com

Comment déclarer un caractère statique const * dans votre fichier d'en-tête?

Je voudrais 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 = "sommething";

Ce qui m'amène avec l'erreur de compilation suivante:

erreur C2864: 'SomeClass :: SOMETHING': seuls les membres de données intégrales const stat 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?

56
Mark

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

Dans votre en-tê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 intégrale 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 portée. Le membre doit toujours être défini dans une étendue d'espace de noms s'il est utilisé dans le programme et la définition d'étendue d'espace de noms ne doit pas contenir d'initialiseur.

66
KeatsPeeks

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

Lorsqu'un objet est utilisé comme une valeur l (c'est-à-dire comme quelque chose qui a une adresse en mémoire), il doit satisfaire à la "règle de définition unique" (ODR), c'est-à-dire qu'il doit être défini dans une et une seule unité de traduction. Le compilateur ne peut pas et ne décidera pas dans quelle unité de traduction définir cet objet. C'est votre responsabilité. En définissant cet objet quelque part, vous ne le définissez pas seulement, vous dites en fait au compilateur que vous souhaitez le définir ici, dans ceci spécifique unité de traduction.

Pendant ce temps, en langage C++, les constantes intégrales ont un statut spécial. Ils peuvent former des expressions constantes intégrales (ICE). Dans les ICE, les constantes intégrales sont utilisées comme des valeurs ordinaires, et non comme objets (c'est-à-dire qu'il n'est pas pertinent de savoir si cette valeur intégrale a une adresse dans le stockage ou non). 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. À cause de cela, les constantes intégrales ont reçu un traitement spécial: il a été permis d'inclure leurs initialiseurs dans le fichier d'en-tête, et l'obligation de fournir une définition a été assouplie (d'abord de facto, puis de jure).

D'autres types constants n'ont pas de telles propriétés. D'autres types constants sont pratiquement toujours utilisés en tant que valeurs l (ou du moins ne peuvent pas participer aux ICE ou à quelque chose de similaire à l'ICE), ce qui signifie qu'ils nécessitent une définition. Le reste suit.

27
AnT

L'erreur est que vous ne pouvez pas initialiser un static const char* au sein de 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:

// En tête de fichier

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

// fichier cpp

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

Si cela vous semble ennuyeux, pensez-y parce que l'initialisation ne peut apparaître que dans une seule unité de traduction. Si c'était dans la définition de classe, cela serait généralement inclus dans plusieurs fichiers. Les entiers constants sont un cas spécial (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, un char* la variable pointe vers un objet réel en mémoire, qui doit vraiment exister, et c'est la définition (y compris l'initialisation) qui fait exister l'objet. La "règle d'une définition" signifie que vous ne voulez donc pas la 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 n'ont pas pu être liés ensemble, même si la chaîne contient les mêmes caractères dans les deux, car selon les règles C++ actuelles, vous avez défini deux objets différents avec le même nom, et ce n'est pas légal. Le fait qu'ils contiennent les mêmes personnages ne les rend pas légaux.

17
Steve Jessop

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

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


Notes:

  • constexpr fait de SOMETHING 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;
    
15
Ignitor
class A{
public:
   static const char* SOMETHING() { return "something"; }
};

Je le fais tout le temps - en particulier pour les paramètres par défaut const chers.

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

Si vous utilisez Visual C++, vous pouvez le faire de manière non portable en utilisant des astuces pour 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 objets, l'éditeur de liens n'en sélectionnera 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.

3
asveikau

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

(Remarque, ceci est un exemple laid, mais fonctionne mot pour mot au moins dans g ++ 4.6.1.)

(fichier values.hpp)

#include <string>

template<int dummy>
class tValues
{
public:
   static const char* myValue;
};

template <int dummy> const char* tValues<dummy>::myValue = "This is a value";

typedef tValues<0> Values;

std::string otherCompUnit(); // test from other compilation unit

(main.cpp)

#include <iostream>
#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::string otherCompUnit () {
   return std::string(Values::myValue);
}

Compilez (par exemple g ++ -o main main.cpp other.cpp && ./main) et voyez 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";
3
Torlack

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

Si un membre de données statiques est de type const intégrale 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 étendue d'espace de nom s'il est utilisé dans le programme et la définition d'étendue d'espace de nom 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 les objets non locaux (3.6.2, 3.6.3).

Vous devez donc écrire quelque part dans le fichier cpp:

const char* SomeClass::SOMETHING = "sommething";
1

Pour répondre à la question pourquoi, les types intégraux sont spéciaux en ce qu'ils ne sont pas une référence à un objet 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 était de gérer les valeurs en dehors du système objet et de la manière la plus efficace et "en ligne" possible.

Cela n'explique pas exactement pourquoi ils sont autorisés comme initialiseurs dans un type, mais pensez-y comme essentiellement un #define et alors cela aura du sens en tant que partie du type et non partie de l'objet.

0
DigitalRoss