web-dev-qa-db-fra.com

Chaîne de constante statique (membre de la classe)

J'aimerais avoir une constante statique privée pour une classe (dans ce cas, une usine de formes).

J'aimerais avoir quelque chose du genre.

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

Malheureusement, le compilateur C++ (g ++) génère toutes sortes d’erreurs, telles que:

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

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

erreur: rendre ‘RECTANGLE’ statique

Cela me dit 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.

420
lb.

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;
};

puis

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

La syntaxe que vous vouliez utiliser à l’origine (initialiseur dans la définition de classe) n’est autorisée que pour les types intégrale et enum.


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

// 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.

441
AnT

En C++ 11, vous pouvez faire maintenant:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
145
abyss.7

Dans les définitions de classe, vous ne pouvez que déclarer membres statiques. Ils doivent être définis en dehors de la classe. Pour les constantes intégrales au moment de la compilation, la norme indique qu'il est possible d'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'intérêt d'utiliser std :: string sur const char [] pour les constantes. std :: string est agréable et tout, mais cela nécessite une initialisation dynamique. Donc, si vous écrivez quelque chose comme

const std::string foo = "hello";

à la portée de l'espace de noms, le constructeur de foo sera exécuté juste avant l'exécution des démarrages principaux. Ce constructeur créera une copie de la constante "hello" dans la mémoire heap. Sauf si vous avez vraiment besoin de RECTANGLE pour être un std :: string, vous pouvez 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.

À la vôtre, s.

33
sellibitze

Ceci est juste 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 cela soit recommandé.

16
GManNickG

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 réponse de abyss.7 : Celui-ci définit un objet std::string réel, pas un const char*

13
Oz Solomon

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. se référer à la réponse de @AndreyT

7
aJ.

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 choisir le fichier objet généré dans lequel le placer. Vous devez donc prendre la décision.

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 dans .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 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 ne peut 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 en-têtes contiendra {""} ou {".h", ".hpp"}, en fonction de l'ordre d'initialisation créé par l'éditeur de liens.

Comme mentionné par @ abyss.7, vous pouvez également utiliser constexpr si 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::string, il en résultera une surcharge car un nouvel objet std::string 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.
}
6
Marko Mahnič

Vous pouvez soit opter pour la solution const char* mentionnée ci-dessus, mais si vous avez constamment besoin de chaîne, vous aurez beaucoup de temps système.
D'autre part, une chaîne statique nécessite une initialisation dynamique. Par conséquent, si vous souhaitez utiliser sa valeur lors de l'initialisation d'une autre variable globale/statique, vous risquez de rencontrer le problème de l'ordre d'initialisation. Pour éviter cela, la solution la plus économique 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(). Étant donné que tout thread peut uniquement être démarré par main() et que A_s_initialized est initialisé avant main(), vous n'avez pas besoin de verrous, même dans un environnement multithread. A_s_initialized vaut 0 par défaut (avant l'initialisation dynamique). Par conséquent, si vous utilisez getS() avant l'initialisation de s, vous appelez la fonction init en toute sécurité.

Btw, dans la réponse ci-dessus: "static statique std :: string RECTANGLE () const", les fonctions statiques ne peuvent pas être const car elles ne peuvent pas changer l'état si un objet existe quand même (il n'y a pas ce pointeur).

4
user2806882

La norme actuelle ne permet cette initialisation que pour les types d'intégrale constante constante. Donc, vous devez faire comme AndreyT a expliqué. Cependant, cela sera disponible dans le prochain standard via = nouvelle syntaxe d'initialisation des membres .

4
Leandro T. C. Melo

possible juste faire:

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

ou

#define RECTANGLE "rectangle"
4
chikuba

Avance rapide jusqu'en 2018 et C++ 17.

  • n'utilisez pas std :: string, utilisez les littéraux std :: string_view
  • s'il vous plaît notez le ci-dessous 'constexpr'. C'est aussi un mécanisme de "compilation".
  • non en ligne ne signifie pas répétition
  • aucun fichier cpp n'est nécessaire pour cela
  • static_assert 'fonctionne' à 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 se trouve un citoyen C++ standard approprié et légal. Il peut être facilement impliqué dans tous les algorithmes, conteneurs, utilitaires et autres de std ::. 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

1
user10133158