Come inizializzare membri statici privati ​​in C ++?


521

Qual è il modo migliore per inizializzare un membro di dati statico privato in C ++? Ho provato questo nel mio file di intestazione, ma mi dà strani errori del linker:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Immagino che ciò sia dovuto al fatto che non riesco a inizializzare un membro privato esterno alla classe. Qual è il modo migliore per farlo?


2
Ciao Jason. Non ho trovato un commento sull'inizializzazione predefinita dei membri statici (specialmente quelli integrali). In effetti è necessario scrivere int foo :: i in modo che il linker possa trovarlo, ma verrà automaticamente inizializzato con 0! Questa riga sarebbe sufficiente: int foo :: i; (Questo è valido per tutti gli oggetti memorizzati nella memoria statica, il linker è incaricato di inizializzare gli oggetti statici.)
Nico,

1
Le risposte di seguito non si applicano per una classe modello. Dicono: l'inizializzazione deve andare nel file sorgente. Per una classe modello, questo non è né possibile né necessario.
Joachim W

7
C ++ 17 consente inizializzazione linea di membri dati statici (anche per i tipi non interi): inline static int x[] = {1, 2, 3};. Vedi en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Risposte:


557

La dichiarazione di classe dovrebbe essere nel file di intestazione (o nel file di origine se non condivisa).
File: foo.h

class foo
{
    private:
        static int i;
};

Ma l'inizializzazione dovrebbe essere nel file sorgente.
File: foo.cpp

int foo::i = 0;

Se l'inizializzazione si trova nel file di intestazione, ogni file che include il file di intestazione avrà una definizione del membro statico. Pertanto durante la fase di collegamento verranno visualizzati errori del linker poiché il codice per inizializzare la variabile verrà definito in più file di origine. L'inizializzazione di static int ideve essere eseguita al di fuori di qualsiasi funzione.

Nota: Matt Curtis: punti che C ++ permette la semplificazione di quanto sopra, se la variabile membro statica è di tipo int const (ad esempio int, bool, char). È quindi possibile dichiarare e inizializzare la variabile membro direttamente all'interno della dichiarazione di classe nel file di intestazione:

class foo
{
    private:
        static int const i = 42;
};

4
Sì. Ma presumo che la domanda sia stata semplificata. Tecnicamente la dichiarazione e la definizione possono essere tutte in un unico file sorgente. Ma ciò limita quindi l'uso della classe da parte di altre classi.
Martin York,

11
in realtà non solo POD, deve essere anche un tipo int (int, short, bool, char ...)
Matt Curtis,

9
Si noti che questa non è solo una questione di come viene inizializzato il valore: i tipi integrali const definiti in questo modo possono essere trasformati in costanti di tempo di compilazione dall'implementazione. Questo non è sempre quello che vuoi, poiché aumenta la dipendenza binaria: il codice client deve essere ricompilato se il valore cambia.
Steve Jessop,

5
@Martin: oltre alla correzione s / POD / tipo integrale /, se l'indirizzo viene mai preso, allora deve esserci anche una definizione. Per quanto strano possa sembrare, la dichiarazione con l'inizializzatore, nella definizione della classe, non è una definizione. L' idioma const constato fornisce una soluzione alternativa per i casi in cui è necessaria la definizione in un file di intestazione. Un'altra soluzione alternativa è una funzione che produce il valore di una costante statica locale. Saluti e hth.,
Saluti e hth. - Alf

3
È possibile aggiungere un chiarimento che int foo :: i = 0; non dovrebbe essere all'interno di una funzione (inclusa la funzione principale). L'ho avuto all'inizio della mia funzione principale e non mi piace.
qwerty9967,

89

Per una variabile :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Questo perché foo::inel tuo programma può esserci solo un'istanza di . È una specie di equivalente di extern int iin un file di intestazione e int iin un file di origine.

Per una costante puoi mettere il valore direttamente nella dichiarazione di classe:

class foo
{
private:
    static int i;
    const static int a = 42;
};

2
Questo è un punto valido Aggiungerò anche questa mia spiegazione. Ma va notato che questo funziona solo per i tipi POD.
Martin York,

Da quando, C ++ permette di essere semplicemente bravo con la dichiarazione in classe e nessuna definizione per i tipi integrali. Da quando C ++ 98 stesso o C ++ 03 o quando? Si prega di condividere link autentici per favore. La formulazione standard C ++ non è sincronizzata con i compilatori. Indicano che il membro deve essere ancora definito se utilizzato. Quindi, non ho bisogno del
preventivo

1
Mi chiedo perché qui le privatevariabili possano essere inizializzate al di fuori della Classe, questo può essere fatto anche per variabili non statiche.
Krishna Oza,

Hai trovato la spiegazione? @Krishna_Oza
nn0p

@ nn0p non ancora, ma l'inizializzazione di variabili private non statiche all'esterno Classnon ha alcun senso in Cpp.
Krishna Oza,

42

A partire da C ++ 17, i membri statici possono essere definiti nell'intestazione con la parola chiave inline .

http://en.cppreference.com/w/cpp/language/static

"Un membro di dati statici può essere dichiarato in linea. Un membro di dati statici in linea può essere definito nella definizione di classe e può specificare un inizializzatore di membro predefinito. Non è necessaria una definizione fuori classe:"

struct X
{
    inline static int n = 1;
};

1
Ciò è possibile dal C ++ 17, che è attualmente in corso per diventare il nuovo standard.
Grebu,

31

Per i futuri spettatori di questa domanda, voglio sottolineare che dovresti evitare ciò che suggerisce monkey0506 .

I file di intestazione sono per le dichiarazioni.

I file di intestazione vengono compilati una volta per ogni .cppfile che li direttamente o indirettamente #includese il codice al di fuori di qualsiasi funzione viene eseguito all'inizializzazione del programma, prima main().

Mettendo: foo::i = VALUE;nell'intestazione, foo:iverrà assegnato il valore VALUE(qualunque esso sia) per ogni .cppfile, e queste assegnazioni avverranno in un ordine indeterminato (determinato dal linker) prima che main()venga eseguito.

E se dovessimo #define VALUEessere un numero diverso in uno dei nostri .cppfile? Compilerà bene e non avremo modo di sapere quale vince fino a quando non avremo eseguito il programma.

Non inserire mai il codice eseguito in un'intestazione per lo stesso motivo per cui non hai mai #includeun .cppfile.

include guards (che sono d'accordo che dovresti sempre usare) ti protegge da qualcosa di diverso: la stessa intestazione viene indirettamente #included più volte durante la compilazione di un singolo .cppfile


2
Hai ragione su questo ovviamente, tranne nel caso di un modello di classe (che non è stato chiesto, ma mi capita di avere a che fare con molto). Pertanto, se la classe è completamente definita e non un modello di classe, inserire questi membri statici in un file CPP separato, ma per i modelli di classe la definizione deve trovarsi nella stessa unità di traduzione (ad esempio, il file di intestazione).
monkey0506,

@ monkey_05_06: sembra proprio un argomento per evitare un membro statico nel codice modello: finisci già con un membro statico per ogni istanza della classe. il problema è aggravato dalla possibilità di compilare l'intestazione in più file cpp ... È possibile ottenere una serie di definizioni contrastanti.
Joshua Clayton,

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Questo collegamento descrive le istanze dei template statici nella funzione principale, che è più pulita, se un po 'un peso.
Joshua Clayton,

1
La tua discussione è davvero enorme. Innanzitutto non è possibile #define VALUE perché il nome della macro non è un identificatore valido. E anche se tu potessi - chi lo farebbe? I file di intestazione sono per la dichiarazione -? Dai ... L'unico caso in cui dovresti evitare di inserire valori nell'intestazione è combattere odr-used. E l'inserimento del valore nell'intestazione può comportare una ricompilazione non necessaria ogni volta che è necessario modificarlo.
Aleksander Fular

20

Con un compilatore Microsoft [1], le variabili statiche non intsimili possono anche essere definite in un file di intestazione, ma al di fuori della dichiarazione di classe, utilizzando lo specifico Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Nota che non sto dicendo che è buono, dico solo che può essere fatto.

[1] Al giorno d'oggi, più compilatori di MSC supportano __declspec(selectany)- almeno gcc e clang. Forse anche di più.


17
int foo::i = 0; 

È la sintassi corretta per l'inizializzazione della variabile, ma deve andare nel file di origine (.cpp) anziché nell'intestazione.

Poiché si tratta di una variabile statica, il compilatore deve crearne una sola copia. Devi avere una riga "int foo: i" in qualche punto del tuo codice per indicare al compilatore dove inserirlo, altrimenti otterrai un errore di collegamento. Se è presente in un'intestazione, otterrai una copia in ogni file che include l'intestazione, quindi ottieni moltiplicatori di errori definiti dal linker.


12

Non ho abbastanza rappresentante qui per aggiungere questo come commento, ma IMO è comunque un buon stile scrivere le tue intestazioni con le protezioni #include , che come notato da Paranaix poche ore fa impedirebbe un errore a definizione multipla. A meno che non si stia già utilizzando un file CPP separato, non è necessario utilizzarne uno solo per inizializzare membri non integrali statici.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Non vedo la necessità di utilizzare un file CPP separato per questo. Certo che puoi, ma non c'è motivo tecnico per cui dovresti farlo.


21
#include guards impedisce solo più definizioni per unità di traduzione.
Paul Fultz II,

3
per quanto riguarda il buon stile: è necessario aggiungere un commento sull'endif di chiusura:#endif // FOO_H
Riga

9
Funziona solo se hai una sola unità di compilazione che include foo.h. Se due o più cpps includono foo.h, che è una situazione tipica, ogni cpp dichiarerebbe la stessa variabile statica in modo che il linker si lamenterebbe con la definizione multipla di `foo :: i 'a meno che non si usi una compilazione di pacchetti con i file (compilare solo un file che include tutti i cpps). Ma sebbene la compilazione dei pacchetti sia ottima, la soluzione al problema è dichiarare (int foo :: i = 0;) in un cpp!
Alejadro Xalabarder,

1
O semplicemente usa#pragma once
tambre il

12

Se vuoi inizializzare un tipo composto (stringa fe) puoi fare qualcosa del genere:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

Poiché ListInitializationGuardè una variabile statica all'interno del SomeClass::getList()metodo, verrà costruita una sola volta, il che significa che il costruttore viene chiamato una volta. Questo initialize _listvaria in base al valore necessario. Ogni chiamata successiva a getListrestituirà semplicemente l' _listoggetto già inizializzato .

Ovviamente devi accedere _listall'oggetto sempre chiamando il getList()metodo.


1
Ecco una versione di questo idioma che non richiede la creazione di un metodo per oggetto membro: stackoverflow.com/a/48337288/895245
Ciro Santilli郝海东冠状病六四事件法轮功

9

Modello di costruttore statico C ++ 11 che funziona per più oggetti

È stato proposto un linguaggio su: https://stackoverflow.com/a/27088552/895245, ma qui è disponibile una versione più pulita che non richiede la creazione di un nuovo metodo per membro.

main.cpp

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub a monte .

Compila ed esegui:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Vedi anche: costruttori statici in C ++? Devo inizializzare oggetti statici privati

Testato su Ubuntu 19.04.

Variabile inline C ++ 17

Citato su: https://stackoverflow.com/a/45062055/895245 ma ecco un esempio eseguibile su più file per renderlo ancora più chiaro: come funzionano le variabili inline?


5

È inoltre possibile includere l'assegnazione nel file di intestazione se si utilizzano protezioni di intestazione. Ho usato questa tecnica per una libreria C ++ che ho creato. Un altro modo per ottenere lo stesso risultato è utilizzare metodi statici. Per esempio...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

Il codice sopra ha il "bonus" di non richiedere un file CPP / sorgente. Ancora una volta, un metodo che uso per le mie librerie C ++.


4

Seguo l'idea di Karl. Mi piace e ora lo uso anche io. Ho cambiato un po 'la notazione e ho aggiunto alcune funzionalità

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

questo produce

mystatic value 7
mystatic value 3
is my static 1 0

3

Funziona anche nel file privateStatic.cpp:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic

3

Che dire di un set_default()metodo?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Dovremmo usare solo il set_default(int x)metodo e la nostra staticvariabile verrebbe inizializzata.

Ciò non sarebbe in disaccordo con il resto dei commenti, in realtà segue lo stesso principio di inizializzazione della variabile in un ambito globale, ma utilizzando questo metodo lo rendiamo esplicito (e facile da capire) invece di avere la definizione della variabile sospesa lì.


3

Il problema del linker che hai riscontrato è probabilmente causato da:

  • Fornendo la definizione di membri di classe e statici nel file di intestazione,
  • Inclusa questa intestazione in due o più file di origine.

Questo è un problema comune per coloro che iniziano con C ++. Il membro di classe statico deve essere inizializzato in una singola unità di traduzione, cioè in un singolo file sorgente.

Sfortunatamente, il membro di classe statico deve essere inizializzato al di fuori del corpo della classe. Ciò complica la scrittura di codice di sola intestazione e, quindi, sto usando un approccio abbastanza diverso. È possibile fornire l'oggetto statico tramite la funzione di classe statica o non statica, ad esempio:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};

1
Sono ancora un n00b completo per quanto riguarda il C ++, ma questo mi sembra geniale, grazie mille! Ottengo la perfetta gestione del ciclo di vita dell'oggetto singleton gratuitamente.
Rafael Kitover,

2

Un modo "vecchio stile" per definire le costanti è sostituirle con un enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

In questo modo non richiede fornire una definizione, e evita di fare la costante lvalue , che può risparmiare qualche mal di testa, ad esempio, quando si accidentalmente ODR-uso di esso.


1

Volevo solo menzionare qualcosa di un po 'strano per me quando l'ho incontrato per la prima volta.

Avevo bisogno di inizializzare un membro di dati statici privati ​​in una classe modello.

nel .h o .hpp, è simile a questo per inizializzare un membro di dati statici di una classe modello:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

0

Questo serve al tuo scopo?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
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.