Chaîne constante statique (membre de la classe)

445

Je voudrais avoir une constante statique privée pour une classe (dans ce cas, une fabrique de formes).

J'aimerais avoir quelque chose du genre.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

Malheureusement, j'obtiens toutes sortes d'erreurs du compilateur C ++ (g ++), telles que:

ISO C ++ interdit l'initialisation du membre 'RECTANGLE'

initialisation en classe non valide d'un membre de données statiques de type non intégral 'std :: string'

erreur: rendre 'RECTANGLE' statique

Cela m'indique que ce type de conception de membre n'est pas conforme à la norme. Comment avez-vous une constante littérale privée (ou peut-être publique) sans avoir à utiliser une directive #define (je veux éviter la laideur de la globalité des données!)

Toute aide est appréciée.

kg.
la source
15
Merci pour toutes vos bonnes réponses! Vive SO!
lb
Quelqu'un peut-il me dire ce qu'est un type «intégral»? Merci beaucoup.
lb
1
Les types intégraux font référence aux types qui représentent des nombres entiers. Voir publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
bleater
Une chaîne statique privée dans votre usine n'est pas une bonne solution - considérez que vos clients d'usine devront savoir quelles formes sont prises en charge, donc au lieu de la garder dans une statique privée, placez-les dans un espace de noms séparé en tant que const statique std :: string RECTANGLE = "Rectangle ".
LukeCodeBaker
si votre classe est une classe modèle, consultez stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Réponses:

470

Vous devez définir votre membre statique en dehors de la définition de classe et y fournir l'initialiseur.

Première

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

et alors

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

La syntaxe que vous essayiez à l'origine d'utiliser (initialiseur dans la définition de classe) n'est autorisée qu'avec les types intégral et enum.


À partir de C ++ 17, vous avez une autre option, qui est assez similaire à votre déclaration d'origine: les variables en ligne

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

Aucune définition supplémentaire n'est nécessaire.

Ou au lieu de constvous pouvez le déclarer constexprdans cette variante. Explicite inlinene serait plus nécessaire, car constexprimplique inline.

Fourmi
la source
8
De plus, s'il n'est pas nécessaire d'utiliser une chaîne STL, vous pouvez tout aussi bien définir un caractère const *. (moins de frais généraux)
KSchmidt
50
Je ne suis pas sûr que ce soit toujours moins de frais généraux - cela dépend de l'utilisation. Si ce membre est destiné à être passé comme argument aux fonctions qui prennent const string &, il y aura une création temporaire pour chaque appel vs une création d'objet chaîne pendant l'initialisation. La surcharge IMHO pour la création d'un objet chaîne statique est négligeable.
Tadeusz Kopec
23
Je préfère également utiliser std :: string tout le temps. Les frais généraux sont négligeables, mais vous avez beaucoup plus d'options et sont beaucoup moins susceptibles d'écrire des trucs idiots comme "magic" == A :: RECTANGLE uniquement pour comparer leur adresse ...
Matthieu M.
9
le char const*a la bonté qu'il est initialisé avant toute initialisation dynamique. Donc, dans le constructeur de n'importe quel objet, vous pouvez compter sur RECTANGLEavoir déjà été initialisé.
Johannes Schaub - litb
8
@cirosantilli: Parce que depuis le début des temps en C ++ les initialiseurs faisaient partie des définitions , pas des déclarations . Et la déclaration de membre de données à l'intérieur de la classe n'est que cela: une déclaration. (D'un autre côté, une exception a été faite pour les membres const integr et enum, et en C ++ 11 - pour les membres const de types littéraux .)
AnT
153

En C ++ 11, vous pouvez maintenant:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
abîme.7
la source
30
Malheureusement, cette solution ne fonctionne pas pour std :: string.
HelloWorld
2
Notez que 1. cela ne fonctionne qu'avec des littéraux et 2. ce n'est pas conforme au standard, bien que Gnu / GCC compile les amendes, les autres compilateurs lanceront une erreur. La définition doit être dans le corps.
ManuelSchneid3r
2
@ ManuelSchneid3r Comment est-ce exactement "non conforme aux normes"? Cela ressemble à une initialisation d' accolade ou d'égalité C ++ 11 standard pour moi.
underscore_d
3
@rvighne, non c'est incorrect. constexprimplique constpour var, pas pour taper it points. C'est à dire static constexpr const char* constla même chose que static constexpr const char*, mais pas la même chose que static constexpr char*.
midenok
2
@ abyss.7 - Merci pour votre réponse, et j'en ai une autre s'il vous plaît: pourquoi doit-elle être statique?
Guy Avraham
34

Dans les définitions de classe, vous ne pouvez déclarer que des membres statiques. Ils doivent être définis en dehors de la classe. Pour les constantes intégrales au moment de la compilation, la norme fait l'exception que vous pouvez «initialiser» les membres. Ce n'est toujours pas une définition, cependant. Prendre l'adresse ne fonctionnerait pas sans définition, par exemple.

Je voudrais mentionner que je ne vois pas l'avantage d'utiliser std :: string sur const char [] pour les constantes . std :: string est sympa et tout mais nécessite une initialisation dynamique. Donc, si vous écrivez quelque chose comme

const std::string foo = "hello";

au niveau de l'espace de noms, le constructeur de foo sera exécuté juste avant l'exécution du démarrage principal et ce constructeur créera une copie de la constante "hello" dans la mémoire du tas. Sauf si vous avez vraiment besoin que RECTANGLE soit une chaîne std ::, vous pourriez tout aussi bien écrire

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

Là! Pas d'allocation de tas, pas de copie, pas d'initialisation dynamique.

Santé, art.

sellibitze
la source
1
Ceci est une réponse pré C ++ 11. Utilisez C ++ standard et utilisez std :: string_view.
1
C ++ 11 n'a pas std :: string_view.
Lukas Salich du
17

Ce ne sont que des informations supplémentaires, mais si vous voulez vraiment la chaîne dans un fichier d'en-tête, essayez quelque chose comme:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Bien que je doute que ce soit recommandé.

GManNickG
la source
Cela a l'air cool :) - je suppose que vous avez une expérience dans d'autres langues que c ++?
lb
5
Je ne le recommanderais pas. Je le fais fréquemment. Cela fonctionne bien et je trouve cela plus évident que de mettre la chaîne dans le fichier d'implémentation. Les données réelles de std :: string se trouvent cependant toujours sur le tas. Je retournerais un const char *, auquel cas vous n'avez pas besoin de déclarer la variable statique pour que la déclaration prenne moins de place (au niveau du code). Mais c'est juste une question de goût.
Zoomulator
15

En C ++ 17, vous pouvez utiliser des variables en ligne :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Notez que ceci est différent de la réponse d' abyss.7 : Celui-ci définit un std::stringobjet réel , pas unconst char*

Oz Solomon
la source
Ne pensez-vous pas que l'utilisation inlinecréera beaucoup de doublons?
shuva
1
@shuva Non, la variable ne sera pas dupliquée .
zett42
8

Pour utiliser cette syntaxe d'initialisation en classe, la constante doit être une constante statique de type intégral ou énumération initialisée par une expression constante.

C'est la restriction. Par conséquent, dans ce cas, vous devez définir une variable en dehors de la classe. renvoyer une réponse de @AndreyT

un J.
la source
7

Les variables statiques de classe peuvent être déclarées dans l'en-tête mais doivent être définies dans un fichier .cpp. En effet, il ne peut y avoir qu'une seule instance d'une variable statique et le compilateur ne peut pas décider dans quel fichier objet généré le placer, vous devez donc prendre la décision à la place.

Pour conserver la définition d'une valeur statique avec la déclaration en C ++ 11, une structure statique imbriquée peut être utilisée. Dans ce cas, le membre statique est une structure et doit être défini dans un fichier .cpp, mais les valeurs sont dans l'en-tête.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

Au lieu d'initialiser des membres individuels, toute la structure statique est initialisée en .cpp:

A::_Shapes A::shape;

Les valeurs sont accessibles avec

A::shape.RECTANGLE;

ou - puisque les membres sont privés et sont destinés à être utilisés uniquement à partir de A - avec

shape.RECTANGLE;

Notez que cette solution souffre toujours du problème de l'ordre d'initialisation des variables statiques. Lorsqu'une valeur statique est utilisée pour initialiser une autre variable statique, la première peut ne pas encore être initialisée.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

Dans ce cas, la variable statique têtes des contiendront {""} ou {".h", ".hpp"}, selon l'ordre d'initialisation créé par l'éditeur de liens.

Comme mentionné par @ abyss.7, vous pouvez également utiliser constexprsi la valeur de la variable peut être calculée au moment de la compilation. Mais si vous déclarez vos chaînes avec static constexpr const char*et que votre programme utilise std::stringautrement, il y aura une surcharge car un nouvel std::stringobjet sera créé chaque fois que vous utiliserez une telle constante:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}
Marko Mahnič
la source
Réponse bien préparée Marko. Deux détails: l'un n'a pas besoin de fichiers cpp pour les membres statiques de la classe, et veuillez également utiliser std :: string_view pour tout type de constantes.
4

La norme actuelle ne permet une telle initialisation que pour les types intégraux à constante statique. Vous devez donc faire comme AndreyT l'a expliqué. Cependant, cela sera disponible dans la prochaine norme via la nouvelle syntaxe d'initialisation des membres .

Leandro TC Melo
la source
4

possible juste faire:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

ou

#define RECTANGLE "rectangle"
chikuba
la source
11
Utiliser #define quand une constante typée peut être utilisée est tout simplement faux.
Artur Czajka
Votre premier exemple est fondamentalement une bonne solution si vous n'en avez pas constexprmais que vous ne pouvez pas créer une fonction statique const.
Frank Puffer
Cette solution doit être évitée. Il crée une nouvelle chaîne à chaque appel. Ce serait mieux:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon
Pourquoi utiliser le conteneur à part entière comme valeur de retour? Utilisez std :: string_vew .. son contenu restera valide dans ce cas. encore mieux utiliser des littéraux de chaîne pour créer et renvoyer la vue de chaîne ... et enfin et surtout, la valeur de retour const n'a aucun sens ni effet ici ... ah oui, et faites-la en ligne, pas statique, dans un en-tête de espace de noms nommé ... et faites en sorte qu'il soit constexpr
4

Vous pouvez soit opter pour la const char*solution mentionnée ci-dessus, mais si vous avez besoin de chaîne tout le temps, vous aurez beaucoup de frais généraux.
D'un autre côté, la chaîne statique a besoin d'une initialisation dynamique, donc si vous souhaitez utiliser sa valeur lors de l'initialisation d'une autre variable globale / statique, vous pourriez rencontrer le problème de l'ordre d'initialisation. Pour éviter cela, le moins cher est d'accéder à l'objet chaîne statique via un getter, qui vérifie si votre objet est initialisé ou non.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

N'oubliez pas d'utiliser uniquement A::getS(). Parce que tout threading ne peut être démarré que par main(), etA_s_initialized est initialisé auparavant main(), vous n'avez pas besoin de verrous même dans un environnement multithread. A_s_initializedest 0 par défaut (avant l'initialisation dynamique), donc si vous utilisezgetS() avant que s soit initialisé, vous appelez la fonction init en toute sécurité.

Btw, dans la réponse ci-dessus: " statique const std :: string RECTANGLE () const ", les fonctions statiques ne peuvent pas être constparce qu'elles ne peuvent pas changer l'état si un objet de toute façon (il n'y a pas ce pointeur).

user2806882
la source
4

Avance rapide vers 2018 et C ++ 17.

  • n'utilisez pas std :: string, utilisez std :: string_view literals
  • veuillez noter le soufflet «constexpr». Il s'agit également d'un mécanisme de "temps de compilation".
  • pas en ligne ne signifie pas la répétition
  • aucun fichier cpp n'est nécessaire pour cela
  • static_assert 'fonctionne' au moment de la compilation uniquement

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

Ci-dessus est un citoyen C ++ standard approprié et légal. Il peut être facilement impliqué dans tous les algorithmes std ::, conteneurs, utilitaires et autres. Par exemple:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

Profitez du C ++ standard


la source
À utiliser std::string_viewpour les constantes uniquement si vous utilisez des string_viewparamètres dans toutes vos fonctions. Si l'une de vos fonctions utilise un const std::string&paramètre, une copie d'une chaîne sera créée lorsque vous passerez une string_viewconstante dans ce paramètre. Si vos constantes sont de type, std::stringles copies ne seront créées ni pour les const std::string&paramètres ni pour les std::string_viewparamètres.
Marko Mahnič
Belle réponse, mais curieux de savoir pourquoi le string_view est renvoyé par une fonction? Ce genre d'astuce était utile avant l' inlinearrivée des variables en C ++ 17 avec leur sémantique ODR. Mais string_view est aussi C ++ 17, donc fait juste constexpr auto some_str = "compile time"sv;le travail (et en fait, ce n'est pas une variable, c'est constexpr, donc inlineest implicite; si vous avez une variable - c'est-à-dire non constexpr- alors le inline auto some_str = "compile time"sv;fera, bien que naturellement une étendue d'espace de noms variable, qui est essentiellement une variable globale, serait rarement une bonne idée).
Perte de mentalité le