Domanda

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;

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

È stato utile?

Soluzione

La dichiarazione di classe dovrebbe essere nel file header (o nel file sorgente 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.

Nota: Matt Curtis: sottolinea che C ++ consente la semplificazione di quanto sopra se la variabile membro statica è di tipo const int (es. 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;
};

Altri suggerimenti

Per una variabile :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Questo perché nel tuo programma può esserci solo un'istanza di foo :: i . È una specie di equivalente di extern int i in un file di intestazione e int i in 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;
};

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

I file di intestazione sono per le dichiarazioni.

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

Inserendo: foo :: i = VALUE; nell'intestazione, foo: i verrà assegnato il valore VALUE (qualunque cosa è) per ogni file .cpp e tali assegnazioni avverranno in un ordine indeterminato (determinato dal linker) prima dell'esecuzione di main () .

E se #define VALUE fosse un numero diverso in uno dei nostri file .cpp ? Compilerà bene e non avremo modo di sapere quale vincerà 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 #include un file .cpp .

include guardie (che sono d'accordo che dovresti sempre usare) ti proteggono da qualcosa di diverso: la stessa intestazione è indirettamente #include d più volte durante la compilazione di un singolo .cpp file

Dal 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 inline può essere definito nella definizione della classe e può specificare un inizializzatore di membro predefinito. Non ha bisogno di una definizione fuori classe: "

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

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

Nota che non sto dicendo che va bene, dico solo che può essere fatto.

[1] Oggigiorno, più compilatori di MSC supportano __declspec (selectany) - almeno gcc e clang. Forse anche di più.

int foo::i = 0; 

È la sintassi corretta per inizializzare la 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 " alcuni dove nel tuo codice dire al compilatore dove metterlo altrimenti si ottiene 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.

Non ho abbastanza rappresentante qui per aggiungere questo come commento, ma IMO è un buon stile per scrivere le tue intestazioni con # include guards comunque, 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 i membri statici non integrali.

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

Se vuoi inizializzare un tipo composto (es. stringa) 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;
    }
};

Dato che ListInitializationGuard è una variabile statica all'interno del metodo SomeClass :: getList () , verrà costruita una sola volta, il che significa che il costruttore viene chiamato una volta. Questo inizializzerà la variabile _list sul valore di cui hai bisogno. Qualsiasi chiamata successiva a getList restituirà semplicemente l'oggetto _list già inizializzato.

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

Puoi anche includere l'assegnazione nel file header se usi le protezioni header. Ho usato questa tecnica per una libreria C ++ che ho creato. Un altro modo per ottenere lo stesso risultato è utilizzare metodi statici. Ad 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 ++.

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 output

mystatic value 7
mystatic value 3
is my static 1 0

Modello di costruttore statico che funziona per più oggetti

Un idioma è stato proposto a: https://stackoverflow.com/a/27088552/895245 ma qui va un versione più pulita che non richiede la creazione di un nuovo metodo per membro e un esempio eseguibile:

#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 up

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

Testato con g ++ -std = c ++ 11 -Wall -Wextra , GCC 7.3, Ubuntu 18.04.

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

Che dire di un metodo set_default () ?

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

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

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

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

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

  • Fornisce sia la definizione di classe che di membro statico nel file di intestazione,
  • Inclusa questa intestazione in due o più file sorgente.

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 della classe statica deve essere inizializzato al di fuori del corpo della classe. Questo 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;
    }
};

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.

in .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;

Una "vecchia scuola" il modo 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 è necessario fornire una definizione ed evita di fare la costante lvalue , che può farti venire il mal di testa , per esempio quando accidentalmente utilizzo ODR .

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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top