web-dev-qa-db-ger.com

Statische Konstantenzeichenfolge (Klassenmitglied)

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

Ich hätte gerne etwas in der Art.

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

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

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

ungültige In-Class-Initialisierung des statischen Datenelements des nichtintegralen Typs "std :: string"

fehler: Statisch machen von "RECHTECK"

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

Jede Hilfe wird geschätzt.

420
lb.

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 ursprünglich verwendete Syntax (Initialisierung innerhalb der Klassendefinition) ist nur bei 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.

441
AnT

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

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

Innerhalb von Klassendefinitionen können Sie nur deklarieren statische Member. Sie müssen definiert außerhalb der Klasse sein. Für Integralkonstanten zur Kompilierungszeit enthält der Standard die Ausnahme, dass Sie Member "initialisieren" können. Es ist jedoch immer noch keine Definition. Das Aufnehmen der Adresse würde zum Beispiel 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 alles, aber es 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 dem Start von main ausgeführt und dieser Konstruktor erstellt eine Kopie der Konstante "hello" im Heap-Speicher. Sofern Sie RECTANGLE nicht wirklich als std :: string benötigen, können Sie auch 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.

33
sellibitze

Dies ist nur eine zusätzliche Information, aber wenn Sie die Zeichenfolge 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.

16
GManNickG

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 abyss.7's answer : Dieses Objekt definiert ein tatsächliches std::string -Objekt, kein const char*

13
Oz Solomon

Um diese klasseninterne Initialisierungssyntax zu verwenden, muss die Konstante eine statische Konstante vom Typ Integral oder Aufzählung sein, die durch einen konstanten Ausdruck initialisiert wird.

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

7
aJ.

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

Um die Definition eines statischen Wertes 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 verwendet wird, um eine andere statische Variable zu initialisieren, ist 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 enthält die statische Variable Header entweder {""} oder {".h", ".hpp"}, abhängig von der Reihenfolge der vom Linker erstellten Initialisierung.

Wie in @ abyss.7 erwähnt, können Sie auch constexpr verwenden, wenn der Wert der Variablen zur Kompilierungszeit berechnet werden kann. Wenn Sie jedoch Ihre Zeichenfolgen mit static constexpr const char* deklarieren und Ihr Programm std::string verwendet, entsteht sonst ein Overhead, da jedes Mal, wenn Sie eine solche Konstante verwenden, ein neues std::string -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.
}
6
Marko Mahnič

Sie können entweder die oben erwähnte const char* -Lösung wählen, aber wenn Sie die ganze Zeit einen String benötigen, werden Sie viel Overhead haben.
Auf der anderen Seite muss eine statische Zeichenfolge dynamisch initialisiert werden. Wenn Sie also ihren Wert während der Initialisierung einer anderen globalen/statischen Variablen verwenden möchten, können Sie auf das Problem der Initialisierungsreihenfolge stoßen. Um dies zu vermeiden, greifen Sie am billigsten über einen Getter auf das statische String-Objekt zu, 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 A::getS() zu verwenden. Da ein Threading nur mit main() gestartet werden kann und A_s_initialized vor main() initialisiert wird, benötigen Sie auch in einer Multithread-Umgebung keine Sperren. A_s_initialized ist standardmäßig 0 (vor der dynamischen Initialisierung). Wenn Sie also getS() verwenden, bevor s initialisiert wird, rufen Sie die init-Funktion sicher auf.

Übrigens: In der obigen Antwort: "static const std :: string RECTANGLE () const" können statische Funktionen nicht const sein, da sie den Status nicht ändern können, wenn es sowieso Objekte gibt (es gibt keine) dieser Zeiger).

4
user2806882

Der aktuelle Standard erlaubt eine solche Initialisierung nur für statische konstante Integraltypen. Also musst du das tun, was AndreyT erklärt hat. Dies wird jedoch im nächsten Standard durch die New Member Initialization Syntax verfügbar sein.

4

möglich einfach machen:

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

oder

#define RECTANGLE "rectangle"
4
chikuba

Schneller Vorlauf bis 2018 und C++ 17.

  • verwenden Sie nicht std :: string, sondern std :: string_view-Literale
  • bitte beachten Sie den "constexpr" -Balg. Dies ist auch ein "Kompilierungszeit" -Mechanismus.
  • no inline bedeutet keine Wiederholung
  • hierzu 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 ordentlicher und gesetzlicher Standard C++ Bürger. Es kann leicht in alle gängigen 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++

1
user10133158