Jak zainicjować prywatnych członków statycznych w C ++?

519

Jaki jest najlepszy sposób na zainicjowanie prywatnego, statycznego elementu danych w C ++? Próbowałem tego w moim pliku nagłówkowym, ale daje mi to dziwne błędy linkera:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Zgaduję, że dzieje się tak, ponieważ nie mogę zainicjować prywatnego członka spoza klasy. Więc jaki jest najlepszy sposób, aby to zrobić?

Jason Baker
źródło
2
Cześć Jason. Nie znalazłem komentarza na temat domyślnej inicjalizacji elementów statycznych (zwłaszcza integralnych). W rzeczywistości musisz napisać int foo :: i, aby linker mógł go znaleźć, ale zostanie automatycznie zainicjowany na 0! Ta linia byłaby wystarczająca: int foo :: i; (Dotyczy to wszystkich obiektów przechowywanych w pamięci statycznej, linker jest odpowiedzialny za inicjalizację obiektów statycznych.)
Nico
1
Poniższe odpowiedzi nie dotyczą klasy szablonów. Mówią: inicjalizacja musi przejść do pliku źródłowego. W przypadku klasy szablonów nie jest to ani możliwe, ani konieczne.
Joachim W,
7
C ++ 17 pozwala na rolkach inicjalizacji statycznych danych (nawet dla typu niecałkowitą) inline static int x[] = {1, 2, 3};. Zobacz en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Odpowiedzi:

556

Deklaracja klasy powinna znajdować się w pliku nagłówkowym (lub w pliku źródłowym, jeśli nie jest udostępniony).
Plik: foo.h

class foo
{
    private:
        static int i;
};

Ale inicjalizacja powinna być w pliku źródłowym.
Plik: foo.cpp

int foo::i = 0;

Jeśli inicjalizacja znajduje się w pliku nagłówkowym, wówczas każdy plik zawierający plik nagłówkowy będzie miał definicję elementu statycznego. Dlatego w fazie łączenia wystąpią błędy linkera, ponieważ kod inicjujący zmienną zostanie zdefiniowany w wielu plikach źródłowych. Inicjalizację static int inależy wykonać poza jakąkolwiek funkcją.

Uwaga: Matt Curtis: Zwraca uwagę, że C ++ umożliwia uproszczenie powyżej jeśli zmienna statyczna jest członkiem const int typu (np int, bool, char). Następnie możesz zadeklarować i zainicjować zmienną składową bezpośrednio w deklaracji klasy w pliku nagłówkowym:

class foo
{
    private:
        static int const i = 42;
};
Martin York
źródło
4
Tak. Ale zakładam, że pytanie zostało uproszczone. Technicznie deklaracja i definicja mogą znajdować się w jednym pliku źródłowym. Ale to ogranicza użycie klasy przez inne klasy.
Martin York,
11
właściwie nie tylko POD, musi to być również typ int (int, short, bool, char ...)
Matt Curtis
9
Zauważ, że nie jest to tylko pytanie o to, jak wartość jest inicjowana: zdefiniowane w ten sposób typy całek const mogą zostać przekształcone w implementację w stałe czasowe kompilacji. Nie zawsze jest to pożądane, ponieważ zwiększa zależność binarną: kod klienta wymaga ponownej kompilacji, jeśli wartość się zmieni.
Steve Jessop,
5
@ Martin: oprócz korekty s / POD / typ całkowy /, jeśli adres jest kiedykolwiek brany, to musi istnieć również definicja. Dziwne, jak może się to wydawać, deklaracja z inicjatorem w definicji klasy nie jest definicją. Matrycy const idiom stanowi obejście dla przypadków, w których potrzebna jest definicja w pliku nagłówka. Innym i prostszym obejściem jest funkcja, która generuje wartość lokalnej stałej statycznej. Pozdrawiam i hth.,
Pozdrawiam i hth. - Alf
3
Możesz dodać wyjaśnienie, że int foo :: i = 0; nie powinien znajdować się w funkcji (w tym w funkcji głównej). Miałem go na początku mojej głównej funkcji i to mi się nie podoba.
qwerty9967
89

Dla zmiennej :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Wynika to z faktu, że foo::iw twoim programie może znajdować się tylko jedna instancja . Jest to coś w rodzaju odpowiednika extern int iw pliku nagłówkowym i int ipliku źródłowym.

Dla stałej możesz umieścić wartość prosto w deklaracji klasy:

class foo
{
private:
    static int i;
    const static int a = 42;
};
Matt Curtis
źródło
2
To jest ważny punkt. Dodam to również moje wyjaśnienie. Należy jednak zauważyć, że działa to tylko w przypadku typów POD.
Martin York,
Od kiedy C ++ pozwala być po prostu dobrym z deklaracją w klasie i bez definicji typów integralnych. Od samego C ++ 98 lub C ++ 03 lub kiedy? Proszę udostępnić autentyczne linki. Standardowe sformułowanie w C ++ nie jest zsynchronizowane z kompilatorami. Wspominają, że członek będzie nadal zdefiniowany, jeśli zostaną użyte. Nie potrzebuję więc cytowania w języku C ++ Standard
smRaj,
1
Zastanawiam się, dlaczego privatezmienne mogą być tutaj inicjowane poza klasą, czy można to zrobić również dla zmiennych niestatycznych.
Krishna Oza,
Czy znalazłeś wyjaśnienie? @Krishna_Oza
nn0p
@ nn0p jeszcze nie, ale inicjalizacja niestatycznych zmiennych prywatnych na zewnątrz Classnie ma żadnego sensu w Cpp.
Krishna Oza
41

Od C ++ 17 elementy statyczne można definiować w nagłówku za pomocą słowa kluczowego inline .

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

„Statyczny element danych może zostać zadeklarowany jako wbudowany. Wbudowany element danych statycznych może być zdefiniowany w definicji klasy i może określać domyślny inicjator elementu. Nie wymaga definicji poza klasą:”

struct X
{
    inline static int n = 1;
};
Zgiń w Sente
źródło
1
Jest to możliwe, ponieważ C ++ 17, który jest obecnie w trakcie tworzenia nowego standardu.
Grebu
31

Dla przyszłych widzów tego pytania chcę zauważyć , że powinieneś unikać sugestii monkey0506 .

Pliki nagłówkowe służą do deklaracji.

Pliki nagłówkowe są kompilowane raz dla każdego .cpppliku, który bezpośrednio lub pośrednio #includes, a kod poza jakąkolwiek funkcją jest uruchamiany przy inicjalizacji programu, wcześniej main().

Umieszczając: foo::i = VALUE;w nagłówku, foo:izostanie przypisana wartość VALUE(cokolwiek to jest) dla każdego .cpppliku, a przypisania te będą wykonywane w nieokreślonej kolejności (określonej przez linker) przed main()uruchomieniem.

Co jeśli będziemy #define VALUEmieć inną liczbę w jednym z naszych .cppplików? Skompiluje się dobrze i nie będziemy mogli wiedzieć, kto wygra, dopóki nie uruchomimy programu.

Nigdy umieścić kod wykonywany w nagłówku tego samego powodu, że nigdy #includew .cpppliku.

uwzględnij strażników (które, jak się zgadzam, zawsze powinieneś używać) chronią cię przed czymś innym: ten sam nagłówek jest pośrednio #included wiele razy podczas kompilacji jednego .cpppliku

Joshua Clayton
źródło
2
Oczywiście masz rację co do tego, z wyjątkiem szablonu klasy (o co się nie pyta, ale zdarza się, że mam dużo do czynienia). Jeśli więc klasa jest w pełni zdefiniowana, a nie szablonem klasy, umieść te elementy statyczne w osobnym pliku CPP, ale w przypadku szablonów klas definicja musi znajdować się w tej samej jednostce tłumaczenia (np. W pliku nagłówkowym).
monkey0506
@ monkey_05_06: To wydaje się być argumentem pozwalającym uniknąć statycznego elementu w kodzie szablonowym: już kończysz z jednym statycznym elementem dla każdej instancji klasy. problem pogłębia się, ponieważ możliwe jest skompilowanie nagłówka w wiele plików CPP ... Możesz uzyskać szereg sprzecznych definicji.
Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/... Ten link przedstawia tworzenie statycznych elementów szablonu w ramach głównej funkcji, która jest czystsza, jeśli jest trochę obciążeniem.
Joshua Clayton
1
Twój argument jest naprawdę ogromny. Po pierwsze nie można # zdefiniować WARTOŚCI, ponieważ nazwa makra musi być poprawnym identyfikatorem. A nawet gdybyś mógł - kto by to zrobił? Pliki nagłówkowe są do deklaracji -? Dalej ... Jedynymi przypadkami, w których należy unikać wpisywania wartości w nagłówku, jest walka z użyciem odr. A umieszczenie wartości w nagłówku może prowadzić do niepotrzebnej ponownej kompilacji za każdym razem, gdy trzeba zmienić wartość.
Aleksander Fular
20

W kompilatorze Microsoft [1] zmienne statyczne, które nie są intpodobne, można również zdefiniować w pliku nagłówkowym, ale poza deklaracją klasy, przy użyciu specyfikacji Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Zauważ, że nie mówię, że to jest dobre, po prostu mówię, że można to zrobić.

[1] Obecnie więcej kompilatorów niż wsparcie MSC __declspec(selectany)- przynajmniej gcc i clang. Może nawet więcej.

Johann Gerell
źródło
17
int foo::i = 0; 

Jest poprawną składnią do inicjowania zmiennej, ale musi ona znajdować się w pliku źródłowym (.cpp), a nie w nagłówku.

Ponieważ jest to zmienna statyczna, kompilator musi utworzyć tylko jedną jej kopię. Musisz mieć wiersz „int foo: i” gdzieś w kodzie, aby poinformować kompilator, gdzie go umieścić, w przeciwnym razie pojawi się błąd łącza. Jeśli znajduje się w nagłówku, otrzymasz kopię w każdym pliku, który zawiera nagłówek, więc otrzymaj wielokrotnie zdefiniowane błędy symboli od linkera.

David Dibben
źródło
12

Nie mam tutaj wystarczającej liczby przedstawicieli, aby dodać to jako komentarz, ale IMO to dobry styl, aby pisać nagłówki za pomocą #include guards , co, jak zauważył Paranaix kilka godzin temu, zapobiegłoby błędowi wielu definicji. O ile nie używasz już osobnego pliku CPP, nie trzeba go używać do inicjowania statycznych niezintegrowanych elementów.

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Nie widzę potrzeby używania do tego osobnego pliku CPP. Jasne, że tak, ale nie ma technicznego powodu, dla którego powinieneś.

małpa0506
źródło
21
#include strażniki po prostu zapobiegają wielu definicjom na jednostkę tłumaczeniową.
Paul Fultz II,
3
odnośnie dobrego stylu: należy dodać komentarz do końcowego zakończenia:#endif // FOO_H
Riga
9
Działa to tylko wtedy, gdy masz tylko jedną jednostkę kompilacji, która zawiera foo.h. Jeśli dwa lub więcej cpps zawiera foo.h, co jest typową sytuacją, każde cpp zadeklaruje tę samą zmienną statyczną, więc linker narzeka na wiele definicji `foo :: i ', chyba że użyjesz kompilacji pakietów z plikami (kompilacja tylko jeden plik, który zawiera wszystkie cpps). Ale chociaż kompilacja pakietów jest świetna, rozwiązaniem problemu jest zadeklarowanie (int foo :: i = 0;) w cpp!
Alejadro Xalabarder,
1
Lub po prostu użyj#pragma once
tambre,
12

Jeśli chcesz zainicjować jakiś typ złożony (ciąg znaków), możesz zrobić coś takiego:

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

Ponieważ ListInitializationGuardw SomeClass::getList()metodzie jest zmienna statyczna , zostanie zbudowana tylko raz, co oznacza, że ​​konstruktor jest wywoływany raz. Będzie to initialize _listzmienna wartości trzeba. Każde kolejne wywołanie getListzwróci już zainicjowany _listobiekt.

Oczywiście musisz _listzawsze uzyskiwać dostęp do obiektu, wywołując getList()metodę.

Kris Kwiatkowski
źródło
1
Oto wersja tego idiomu, która nie wymaga utworzenia jednej metody dla obiektu członka: stackoverflow.com/a/48337288/895245
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
9

C ++ 11 wzorzec statycznego konstruktora, który działa dla wielu obiektów

Jeden idiom zaproponowano na stronie : https://stackoverflow.com/a/27088552/895245, ale tutaj pojawia się czystsza wersja, która nie wymaga tworzenia nowej metody dla członka.

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 w górę .

Skompiluj i uruchom:

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

Zobacz także: statyczne konstruktory w C ++? Muszę zainicjować prywatne obiekty statyczne

Testowane na Ubuntu 19.04.

Zmienna wbudowana C ++ 17

Wspomniano na: https://stackoverflow.com/a/45062055/895245, ale oto przykład, który można uruchomić na wielu plikach, aby uczynić go jeszcze bardziej zrozumiałym: Jak działają zmienne wbudowane?

Ciro Santilli
źródło
5

Możesz także dołączyć przypisanie do pliku nagłówka, jeśli używasz osłon nagłówka. Użyłem tej techniki do utworzonej przeze mnie biblioteki C ++. Innym sposobem osiągnięcia tego samego wyniku jest zastosowanie metod statycznych. Na przykład...

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

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

Powyższy kod ma „bonus” polegający na tym, że nie wymaga pliku CPP / źródłowego. Znów metoda, której używam dla moich bibliotek C ++.


źródło
4

Podążam za pomysłem Karla. Podoba mi się i teraz też go używam. Zmieniłem nieco notację i dodałem trochę funkcjonalności

#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));
}

to wychodzi

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
źródło
3

Działa również w pliku 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
Andrzej
źródło
3

Co z set_default()metodą?

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

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

Musielibyśmy tylko użyć tej set_default(int x)metody, a nasza staticzmienna zostałaby zainicjowana.

Nie byłoby to niezgodne z pozostałymi komentarzami, w rzeczywistości jest zgodne z tą samą zasadą inicjowania zmiennej w zakresie globalnym, ale za pomocą tej metody wyrażamy ją jasno (i łatwo ją zrozumieć) zamiast definicji wiszącej tam zmiennej.

Arturo Ruiz Mañas
źródło
3

Napotkany problem z linkerem jest prawdopodobnie spowodowany:

  • Zapewnienie definicji klasy i statycznego elementu członkowskiego w pliku nagłówkowym,
  • Łącznie z tym nagłówkiem w dwóch lub więcej plikach źródłowych.

Jest to powszechny problem dla tych, którzy zaczynają od C ++. Członek klasy statycznej musi być zainicjowany w pojedynczej jednostce tłumaczenia, tj. W jednym pliku źródłowym.

Niestety element klasy statycznej musi zostać zainicjowany poza ciałem klasy. To komplikuje pisanie kodu zawierającego tylko nagłówek, dlatego używam zupełnie innego podejścia. Możesz dostarczyć swój obiekt statyczny za pomocą statycznej lub niestatycznej funkcji klasy, na przykład:

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

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
nikt wyjątkowy
źródło
1
Nadal jestem kompletnym n00b, jeśli chodzi o C ++, ale dla mnie wygląda to świetnie, dziękuję bardzo! Otrzymuję idealne zarządzanie cyklem życia obiektu singletonowego za darmo.
Rafael Kitover,
2

Jednym ze „oldschoolowych” sposobów definiowania stałych jest zastąpienie ich enum:

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

W ten sposób nie wymaga opatrzenia definicji i unika podejmowania stałej lwartość , które można zaoszczędzić trochę głowy, na przykład, gdy przypadkowo ODR wykorzystanie go.

anatolig
źródło
1

Chciałem tylko wspomnieć o czymś trochę dziwnym, kiedy po raz pierwszy to spotkałem.

Musiałem zainicjować prywatnego członka danych statycznych w klasie szablonów.

w .h lub .hpp wygląda to tak, aby zainicjować element danych statycznych klasy szablonu:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Tyler Heers
źródło
0

Czy to służy twojemu celowi?

//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;
}
David Nogueira
źródło