Stringa costante statica (membro della classe)


445

Vorrei avere una costante statica privata per una classe (in questo caso una fabbrica di forme).

Mi piacerebbe avere qualcosa del genere.

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

Sfortunatamente ricevo ogni sorta di errore dal compilatore C ++ (g ++), come ad esempio:

ISO C ++ proibisce l'inizializzazione del membro "RETTANGOLO"

inizializzazione in classe non valida del membro di dati statici di tipo non integrale 'std :: string'

errore: rendere statico 'RETTANGOLO'

Questo mi dice che questo tipo di design dei membri non è conforme allo standard. Come hai una costante letterale privata (o forse pubblica) senza dover usare una direttiva #define (voglio evitare la bruttezza della globalità dei dati!)

Qualsiasi aiuto è apprezzato.


15
Grazie per tutte le tue fantastiche risposte! Lunga vita a SO!
lb.

Qualcuno può dirmi che cos'è un tipo "integrale"? Grazie mille.
lb.

1
Tipi integrali si riferisce a tipi che rappresentano numeri interi. Vedi publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
bleater

La stringa statica privata nella tua fabbrica non è una buona soluzione - considera che i tuoi clienti della fabbrica dovranno sapere quali forme sono supportate, quindi invece di tenerla in statica privata, inseriscili in uno spazio dei nomi separato come static const std :: string RECTANGLE = "Rectangle ".
LukeCodeBaker

se la tua classe è una classe modello, vedi stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Risposte:


471

Devi definire il tuo membro statico al di fuori della definizione della classe e fornire l'inizializzatore lì.

Primo

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

e poi

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

La sintassi che stavi inizialmente cercando di utilizzare (inizializzatore all'interno della definizione della classe) è consentita solo con tipi integrali ed enum.


A partire da C ++ 17 hai un'altra opzione, che è abbastanza simile alla tua dichiarazione originale: variabili in linea

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

Non è necessaria alcuna definizione aggiuntiva.

O invece di constte puoi dichiararlo constexprin questa variante. Esplicito inlinenon sarebbe più necessario, poiché constexprimplica inline.


8
Inoltre, se non è richiesto l'uso di una stringa STL, è possibile anche definire un carattere const *. (meno spese generali)
KSchmidt,

50
Non sono sicuro che sia sempre meno sovraccarico - dipende dall'uso. Se questo membro deve essere passato come argomento alle funzioni che accettano una stringa const &, durante l'inizializzazione verrà creato temporaneamente per ogni chiamata rispetto alla creazione di un oggetto stringa. L'overhead IMHO per la creazione di un oggetto stringa statico è neglible.
Tadeusz Kopec,

23
Preferirei usare sempre anche std :: string. Il sovraccarico è trascurabile, ma hai molte più opzioni e hai molte meno probabilità di scrivere alcune cose folli come "magia" == A :: RETTANGOLO solo per confrontare il loro indirizzo ...
Matthieu M.

9
l' char const*ha la bontà che viene inizializzato prima che tutto l'inizializzazione dinamica è fatto. Quindi, nel costruttore di qualsiasi oggetto, puoi fare affidamento sul fatto RECTANGLEche sono stati inizializzati alreay allora.
Johannes Schaub - litb

8
@cirosantilli: Perché fin dall'inizio dei tempi gli inizializzatori in C ++ facevano parte delle definizioni , non delle dichiarazioni . E la dichiarazione dei membri di dati all'interno della classe è proprio questa: una dichiarazione. (D'altra parte, è stata fatta un'eccezione per i componenti const integrale ed enum, e in C ++ 11 - per i componenti const di tipo letterale .)
AnT

153

In C ++ 11 puoi fare ora:

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

31
Sfortunatamente questa soluzione non funziona con std :: string.
HelloWorld,

2
Si noti che 1. funziona solo con valori letterali e 2. non è conforme allo standard, sebbene Gnu / GCC comporti multe, altri compilatori genereranno un errore. La definizione deve essere nel corpo.
ManuelSchneid3r

2
@ ManuelSchneid3r Com'è esattamente questo "non standard"? A me sembra un'inizializzazione brace -standard pari a C ++ 11 standard .
underscore_d

3
@rvighne, no non è corretto. constexprimplica constper var, non per digitare punta. Cioè static constexpr const char* constè lo stesso di static constexpr const char*, ma non è lo stesso di static constexpr char*.
midenok,

2
@ abyss.7 - Grazie per la risposta, e ne ho un'altra per favore: perché deve essere statica?
Guy Avraham,

34

All'interno delle definizioni di classe è possibile dichiarare solo membri statici. Devono essere definiti al di fuori della classe. Per le costanti integrali in fase di compilazione lo standard fa l'eccezione che è possibile "inizializzare" i membri. Tuttavia, non è ancora una definizione. Prendere l'indirizzo non funzionerebbe senza definizione, per esempio.

Vorrei menzionare che non vedo il vantaggio di usare std :: string over const char [] per le costanti . std :: string è bello e tutto ma richiede l'inizializzazione dinamica. Quindi, se scrivi qualcosa del genere

const std::string foo = "hello";

nell'ambito dello spazio dei nomi il costruttore di foo verrà eseguito subito prima dell'esecuzione degli avvii principali e questo costruttore creerà una copia della costante "ciao" nella memoria dell'heap. A meno che tu non abbia davvero bisogno di RECTANGLE per essere una std :: string potresti anche scrivere

// 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à! Nessuna allocazione di heap, nessuna copia, nessuna inizializzazione dinamica.

Saluti, s.


1
Questa è la risposta pre C ++ 11. Usa C ++ standard e usa std :: string_view.

1
C ++ 11 non ha std :: string_view.
Lukas Salich,

17

Queste sono solo informazioni extra, ma se vuoi davvero la stringa in un file header, prova qualcosa del tipo:

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

        return str;
    }
};

Anche se dubito che sia raccomandato.


Sembra fantastico :) - Immagino che tu abbia uno sfondo in altre lingue oltre al c ++?
lb.

5
Non lo consiglierei. Lo faccio spesso. Funziona bene e lo trovo più ovvio che mettere la stringa nel file di implementazione. Tuttavia, i dati effettivi di std :: string si trovano ancora sull'heap. Restituirei un carattere const *, nel qual caso non è necessario dichiarare la variabile statica in modo che la dichiarazione occupi meno spazio (in termini di codice). Solo una questione di gusti però.
Zoomulator


8

Per utilizzare quella sintassi di inizializzazione in classe, la costante deve essere una const statica di tipo integrale o di enumerazione inizializzata da un'espressione costante.

Questa è la restrizione. Quindi, in questo caso è necessario definire una variabile al di fuori della classe. fare riferimento a risposta da @AndreyT


7

Le variabili statiche della classe possono essere dichiarate nell'intestazione ma devono essere definite in un file .cpp. Questo perché può esserci solo un'istanza di una variabile statica e il compilatore non può decidere in quale file oggetto generato metterlo, quindi devi prendere la decisione.

Per mantenere la definizione di un valore statico con la dichiarazione in C ++ 11 è possibile utilizzare una struttura statica nidificata. In questo caso il membro statico è una struttura e deve essere definito in un file .cpp, ma i valori sono nell'intestazione.

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

Invece di inizializzare i singoli membri l'intera struttura statica viene inizializzata in .cpp:

A::_Shapes A::shape;

Si accede ai valori con

A::shape.RECTANGLE;

oppure - poiché i membri sono privati ​​e devono essere utilizzati solo da A - con

shape.RECTANGLE;

Si noti che questa soluzione soffre ancora del problema dell'ordine di inizializzazione delle variabili statiche. Quando si utilizza un valore statico per inizializzare un'altra variabile statica, la prima potrebbe non essere ancora inizializzata.

// 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 questo caso le intestazioni delle variabili statiche conterranno {""} o {".h", ".hpp"}, a seconda dell'ordine di inizializzazione creato dal linker.

Come accennato da @ abyss.7, puoi anche utilizzare constexprse il valore della variabile può essere calcolato al momento della compilazione. Ma se dichiari le tue stringhe con static constexpr const char*e il tuo programma usa std::stringaltrimenti ci sarà un overhead perché un nuovo std::stringoggetto verrà creato ogni volta che usi una tale costante:

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

Risposta ben preparata Marko. Due dettagli: uno non ha bisogno di file cpp per i membri della classe statica e si prega di usare std :: string_view per qualsiasi tipo di costante.


4

possibile basta fare:

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

o

#define RECTANGLE "rectangle"

11
L'uso di #define quando è possibile utilizzare una costante digitata è semplicemente sbagliato.
Artur Czajka,

Il tuo primo esempio è sostanzialmente una buona soluzione se non hai, constexprma non puoi fare una funzione statica const.
Frank Puffer,

Questa soluzione dovrebbe essere evitata. Crea una nuova stringa su ogni invocazione. Sarebbe meglio:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon il

Perché usare il contenitore completo come valore di ritorno? Usa std :: string_vew .. il suo contenuto rimarrà valido in questo caso. ancora meglio usare letterali stringa per creare e restituire la vista stringa ... e, ultimo ma non meno importante, il valore di ritorno const non ha alcun significato o effetto qui .. namespace ... e si prega di renderlo constexpr

4

Puoi scegliere la const char*soluzione sopra menzionata, ma se hai sempre bisogno di stringhe, avrai un sacco di spese generali.
D'altra parte, la stringa statica richiede l'inizializzazione dinamica, quindi se si desidera utilizzare il suo valore durante l'inizializzazione di un'altra variabile globale / statica, è possibile che si verifichi il problema dell'ordine di inizializzazione. Per evitare ciò, la cosa più economica è accedere all'oggetto stringa statica tramite un getter, che controlla se l'oggetto è inizializzato o meno.

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

Ricorda di usare solo A::getS(). Poiché qualsiasi threading può solo essere avviato main()e A_s_initializedinizializzato in precedenza main(), non è necessario il blocco anche in un ambiente multithread. A_s_initializedè 0 per impostazione predefinita (prima dell'inizializzazione dinamica), quindi se si utilizza getS()prima dell'inizializzazione di s, si chiama la funzione init in modo sicuro.

A proposito, nella risposta sopra: " static const std :: string RECTANGLE () const ", le funzioni statiche non possono essere constperché non possono cambiare lo stato se qualche oggetto comunque (non c'è questo puntatore).


4

Avanti veloce al 2018 e C ++ 17.

  • non usare std :: string, usa i letterali std :: string_view
  • si prega di notare il muggito "constexpr". Questo è anche un meccanismo di "tempo di compilazione".
  • nessun inline non significa ripetizione
  • non sono necessari file cpp per questo
  • static_assert 'funziona' solo in fase di compilazione

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

    };

Sopra è un cittadino C ++ standard adeguato e legale. Può essere facilmente coinvolto in tutti gli algoritmi std ::, container, utility e simili. Per esempio:

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

Goditi il ​​C ++ standard


Utilizzare std::string_viewper le costanti solo se si utilizzano i string_viewparametri in tutte le funzioni. Se una delle tue funzioni utilizza un const std::string&parametro, verrà creata una copia di una stringa quando passi una string_viewcostante attraverso quel parametro. Se le costanti sono di tipo, std::stringle copie non verranno create né per i const std::string&parametri né per i std::string_viewparametri.
Marko Mahnič,

Bella risposta, ma curioso di sapere perché string_view viene restituito da una funzione? Questo tipo di trucco era utile prima che le inlinevariabili arrivassero in C ++ 17 con la loro semantica ODR. Ma string_view è anche C ++ 17, quindi constexpr auto some_str = "compile time"sv;fa solo il lavoro (e in realtà, non è una variabile, è constexpr, quindi inlineè implicito; se hai una variabile - cioè no constexpr- allora inline auto some_str = "compile time"sv;lo farà, anche se ovviamente un ambito di spazio dei nomi variabile, che è essenzialmente una variabile globale, raramente sarebbe una buona idea).
Perdita di mentalità l'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.