Statische Konstantenzeichenfolge (Klassenmitglied)


444

Ich hätte gerne eine private statische Konstante für eine Klasse (in diesem Fall eine Formfabrik).

Ich hätte gerne so etwas.

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

Leider bekomme ich vom C ++ (g ++) Compiler alle möglichen Fehler, wie zum Beispiel:

ISO C ++ verbietet die Initialisierung des Mitglieds 'RECTANGLE'

Ungültige In-Class-Initialisierung eines statischen Datenelements vom nicht integralen Typ 'std :: string'

Fehler: 'RECTANGLE' wird statisch

Dies sagt mir, dass diese Art von Elementdesign nicht dem Standard entspricht. Wie haben Sie eine private Literalkonstante (oder vielleicht eine öffentliche), ohne eine # define-Direktive verwenden zu müssen (ich möchte die Hässlichkeit der Datenglobalität vermeiden!)

Jede Hilfe wird geschätzt.


15
Vielen Dank für all Ihre tollen Antworten! Es lebe SO!
lb.

Kann mir bitte jemand sagen, was ein "integraler" Typ ist? Vielen Dank.
lb.

1
Integrale Typen beziehen sich auf Typen, die Ganzzahlen darstellen. Siehe publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
Bleater

Private statische Zeichenfolgen in Ihrer Factory sind keine gute Lösung. Beachten Sie, dass Ihre Factory-Clients wissen müssen, welche Formen unterstützt werden. Anstatt sie in privaten statischen Zeichenfolgen zu belassen, fügen Sie sie als statische const std :: string RECTANGLE = "Rectangle in einen separaten Namespace ein ".
LukeCodeBaker

Wenn Ihre Klasse eine Vorlagenklasse ist, siehe stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Antworten:


469

Sie müssen Ihr statisches Element außerhalb der Klassendefinition definieren und dort den Initialisierer bereitstellen.

Zuerst

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

und dann

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

Die Syntax, die Sie ursprünglich verwenden wollten (Initialisierer innerhalb der Klassendefinition), ist nur mit Integral- und Aufzählungstypen zulässig.


Ab C ++ 17 haben Sie eine weitere Option, die Ihrer ursprünglichen Deklaration sehr ähnlich ist: Inline-Variablen

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

Es ist keine zusätzliche Definition erforderlich.

Oder stattdessen constkönnen Sie es constexprin dieser Variante deklarieren . Explizit inlinewäre nicht mehr notwendig, da constexprimpliziert inline.


8
Wenn die Verwendung einer STL-Zeichenfolge nicht erforderlich ist, können Sie auch einfach ein const char * definieren. (weniger Aufwand)
KSchmidt

50
Ich bin mir nicht sicher, ob es immer weniger Overhead gibt - es hängt von der Nutzung ab. Wenn dieses Element als Argument an Funktionen übergeben werden soll, die const string & annehmen, wird für jeden Aufruf vorübergehend eine Objekterstellung für einen String während der Initialisierung erstellt. IMHO-Overhead zum Erstellen eines statischen String-Objekts ist vernachlässigbar.
Tadeusz Kopec

23
Ich würde lieber auch die ganze Zeit std :: string verwenden. Der Overhead ist vernachlässigbar, aber Sie haben viel mehr Möglichkeiten und schreiben viel weniger dumme Dinge wie "Magie" == A :: RECTANGLE, nur um ihre Adresse zu vergleichen ...
Matthieu M.

9
Das char const*hat die Güte, dass es initialisiert wird, bevor alle dynamischen Initialisierungen durchgeführt werden. Im Konstruktor eines Objekts können Sie sich also darauf verlassen RECTANGLE, dass es bereits initialisiert wurde.
Johannes Schaub - litb

8
@cirosantilli: Weil Initialisierer in C ++ von Anfang an Teile von Definitionen waren , keine Deklarationen . Und die Deklaration von Datenelementen innerhalb der Klasse ist genau das: eine Deklaration. (Andererseits wurde eine Ausnahme für const Integral- und Enum-Mitglieder und in C ++ 11 - für const-Mitglieder von Literaltypen gemacht .)
AnT

153

In C ++ 11 können Sie jetzt Folgendes tun:

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

30
Leider funktioniert diese Lösung für std :: string nicht.
HelloWorld

2
Beachten Sie, dass 1. dies nur mit Literalen funktioniert und 2. dies nicht standardkonform ist, obwohl Gnu / GCC Bußgelder einhält, andere Compiler einen Fehler auslösen. Die Definition muss im Körper sein.
ManuelSchneid3r

2
@ ManuelSchneid3r Wie genau ist das "nicht standardkonform"? Es sieht für mich nach einer Standard-C ++ 11 -Initialisierung aus .
underscore_d

3
@rvighne, nein das ist falsch. constexprimpliziert constfür var, nicht für type it points. Dh static constexpr const char* constist das gleiche wie static constexpr const char*, aber nicht das gleiche wie static constexpr char*.
Midenok

2
@ abyss.7 - Danke für deine Antwort, und ich habe bitte eine andere: Warum muss sie statisch sein?
Guy Avraham

34

Innerhalb von Klassendefinitionen können Sie nur statische Mitglieder deklarieren . Diese müssen definiert außerhalb der Klasse. Für Integralkonstanten zur Kompilierungszeit macht der Standard die Ausnahme, dass Sie Mitglieder "initialisieren" können. Es ist jedoch immer noch keine Definition. Das Übernehmen der Adresse würde beispielsweise ohne Definition nicht funktionieren.

Ich möchte erwähnen, dass ich den Vorteil der Verwendung von std :: string gegenüber const char [] für Konstanten nicht sehe . std :: string ist nett und erfordert eine dynamische Initialisierung. Also, wenn du so etwas schreibst

const std::string foo = "hello";

Im Namespace-Bereich wird der Konstruktor von foo unmittelbar vor der Ausführung von main gestartet und dieser Konstruktor erstellt eine Kopie der Konstanten "hello" im Heapspeicher. Wenn Sie RECTANGLE nicht wirklich als std :: string benötigen, können Sie genauso gut schreiben

// 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";

Dort! Keine Heap-Zuordnung, kein Kopieren, keine dynamische Initialisierung.

Prost, s.


1
Dies ist eine Antwort vor C ++ 11. Verwenden Sie Standard-C ++ und std :: string_view.

1
C ++ 11 hat keine std :: string_view.
Lukas Salich

17

Dies sind nur zusätzliche Informationen. Wenn Sie die Zeichenfolge jedoch wirklich in einer Header-Datei haben möchten, versuchen Sie Folgendes:

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

        return str;
    }
};

Obwohl ich bezweifle, dass dies empfohlen wird.


Das sieht cool aus :) - Ich vermute, Sie haben einen Hintergrund in anderen Sprachen als C ++?
lb.

5
Ich würde es nicht empfehlen. Ich mache das häufig. Es funktioniert gut und ich finde es offensichtlicher, als die Zeichenfolge in die Implementierungsdatei einzufügen. Die tatsächlichen Daten von std :: string befinden sich jedoch immer noch auf dem Heap. Ich würde ein const char * zurückgeben. In diesem Fall müssen Sie die statische Variable nicht deklarieren, damit die Deklaration weniger Platz beansprucht (Code). Nur eine Frage des Geschmacks.
Zoomulator

15

In C ++ 17 können Sie Inline-Variablen verwenden :

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

Beachten Sie, dass dies anders ist als die Antwort von abyss.7 : Diese definiert ein tatsächliches std::stringObjekt, nicht einconst char*


Denken Sie nicht, dass die Verwendung inlineviele Duplikate erzeugt?
Shuva

1
@shuva Nein, die Variable wird nicht dupliziert .
zett42

8

Um diese In-Class-Initialisierungssyntax verwenden zu können, muss die Konstante eine statische Konstante vom Integral- oder Aufzählungstyp sein, die durch einen konstanten Ausdruck initialisiert wird.

Dies ist die Einschränkung. In diesem Fall müssen Sie daher eine Variable außerhalb der Klasse definieren. Antwort von @AndreyT


7

Die statischen Klassenvariablen können im Header deklariert werden , müssen jedoch in einer CPP-Datei definiert werden. Dies liegt daran, dass es nur eine Instanz einer statischen Variablen geben kann und der Compiler nicht entscheiden kann, in welche generierte Objektdatei sie eingefügt werden soll, sodass Sie stattdessen die Entscheidung treffen müssen.

Um die Definition eines statischen Werts mit der Deklaration in C ++ 11 beizubehalten, kann eine verschachtelte statische Struktur verwendet werden. In diesem Fall ist das statische Element eine Struktur und muss in einer CPP-Datei definiert werden, die Werte befinden sich jedoch im Header.

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

Anstatt einzelne Elemente zu initialisieren, wird die gesamte statische Struktur in .cpp initialisiert:

A::_Shapes A::shape;

Auf die Werte wird mit zugegriffen

A::shape.RECTANGLE;

oder - da die Mitglieder privat sind und nur von A verwendet werden sollen - mit

shape.RECTANGLE;

Beachten Sie, dass diese Lösung immer noch unter dem Problem der Reihenfolge der Initialisierung der statischen Variablen leidet. Wenn ein statischer Wert zum Initialisieren einer anderen statischen Variablen verwendet wird, wird die erste möglicherweise noch nicht initialisiert.

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

In diesem Fall enthalten die statischen Variablen- Header je nach der vom Linker erstellten Initialisierungsreihenfolge entweder {""} oder {".h", ".hpp"}.

Wie in @ abyss.7 erwähnt, können Sie auch verwenden, constexprwenn der Wert der Variablen zur Kompilierungszeit berechnet werden kann. Wenn Sie jedoch Ihre Zeichenfolgen mit deklarieren static constexpr const char*und Ihr Programm std::stringanderweitig verwendet , entsteht ein Overhead, da std::stringjedes Mal, wenn Sie eine solche Konstante verwenden, ein neues Objekt erstellt wird:

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

Gut vorbereitete Antwort Marko. Zwei Details: Eine benötigt keine CPP-Dateien für statische Klassenmitglieder, und bitte verwenden Sie std :: string_view für jede Art von Konstanten.


4

möglich einfach machen:

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

oder

#define RECTANGLE "rectangle"

11
Die Verwendung von #define, wenn eine typisierte Konstante verwendet werden kann, ist einfach falsch.
Artur Czajka

Ihr erstes Beispiel ist im Grunde eine gute Lösung, wenn Sie keine haben, constexpraber keine statische Funktion erstellen können const.
Frank Puffer

Diese Lösung sollte vermieden werden. Bei jedem Aufruf wird eine neue Zeichenfolge erstellt. Dies wäre besser:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon

Warum den ausgewachsenen Container als Rückgabewert verwenden? Verwenden Sie std :: string_vew .. Der Inhalt bleibt in diesem Fall gültig. Verwenden Sie noch besser String-Literale, um die String-Ansicht zu erstellen und zurückzugeben ... und nicht zuletzt hat der Rückgabewert const hier keine Bedeutung oder Wirkung benannter Namespace ... und bitte machen Sie es zu constexpr

4

Sie können sich entweder für die const char*oben genannte Lösung entscheiden, aber wenn Sie ständig Zeichenfolgen benötigen, werden Sie viel Overhead haben.
Auf der anderen Seite benötigt eine statische Zeichenfolge eine dynamische Initialisierung. Wenn Sie also ihren Wert während der Initialisierung einer anderen globalen / statischen Variablen verwenden möchten, kann das Problem der Initialisierungsreihenfolge auftreten. Um dies zu vermeiden, ist es am billigsten, über einen Getter auf das statische Zeichenfolgenobjekt zuzugreifen, der prüft, ob Ihr Objekt initialisiert ist oder nicht.

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

Denken Sie daran, nur zu verwenden A::getS(). Da Threading nur von gestartet werden main()kann und A_s_initializedzuvor initialisiert wurde main(), benötigen Sie auch in einer Multithread-Umgebung keine Sperren. A_s_initializedist standardmäßig 0 (vor der dynamischen Initialisierung). Wenn Sie also vor der Initialisierung von getS()s verwenden, rufen Sie die Init-Funktion sicher auf.

Übrigens können in der obigen Antwort: " static const std :: string RECTANGLE () const " statische Funktionen nicht sein, constweil sie den Status eines Objekts ohnehin nicht ändern können (es gibt keinen Zeiger).


4

Schneller Vorlauf bis 2018 und C ++ 17.

  • Verwenden Sie nicht std :: string, sondern std :: string_view-Literale
  • Bitte beachten Sie das unten stehende "constexpr". Dies ist auch ein "Kompilierungszeit" -Mechanismus.
  • Kein Inline bedeutet keine Wiederholung
  • Hierfür sind keine CPP-Dateien erforderlich
  • static_assert 'funktioniert' nur zur Kompilierungszeit

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

    };

Oben ist ein richtiger und legaler Standard C ++ Bürger. Es kann leicht in alle std :: -Algorithmen, Container, Dienstprogramme und dergleichen eingebunden werden. Zum Beispiel:

// 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" 
 );

Genießen Sie das Standard-C ++


Verwenden Sie std::string_viewfür Konstanten nur , wenn Sie verwenden string_viewParameter in allen Ihren Funktionen. Wenn eine Ihrer Funktionen einen const std::string&Parameter verwendet, wird eine Kopie einer Zeichenfolge erstellt, wenn Sie eine string_viewKonstante durch diesen Parameter übergeben. Wenn Ihre Konstanten vom Typ sind, werden std::stringdie Kopien weder für const std::string&Parameter noch für std::string_viewParameter erstellt.
Marko Mahnič

Schöne Antwort, aber neugierig, warum die string_view von einer Funktion zurückgegeben wird? Diese Art von Trick war nützlich, bevor inlineVariablen mit ihrer ODR-Semantik in C ++ 17 eintrafen. Aber string_view ist C ++ 17 zu, so dass nur constexpr auto some_str = "compile time"sv;macht die Arbeit (und tatsächlich, es ist nicht eine Variable, dann ist es constexpr, so inlineist implizit, wenn Sie eine Variable haben - also nicht constexpr- dann inline auto some_str = "compile time"sv;wird es tun, obwohl natürlich ein Namespace-scope Variable, die im Wesentlichen eine globale Variable ist, wäre selten eine gute Idee.
Verlustmentalität
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.