Ciąg stały ciąg (członek klasy)

445

Chciałbym mieć prywatną stałą statyczną dla klasy (w tym przypadku fabrykę kształtów).

Chciałbym mieć coś w tym rodzaju.

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

Niestety dostaję różnego rodzaju błędy z kompilatora C ++ (g ++), takie jak:

ISO C ++ zabrania inicjalizacji elementu „RECTANGLE”

niepoprawna inicjalizacja w klasie elementu danych statycznych typu niezintegrowanego „std :: string”

błąd: statyczne ustawienie „RECTANGLE”

Mówi mi to, że tego rodzaju konstrukcja pręta nie jest zgodna ze standardem. Jak masz prywatną literalną stałą (a może publiczną) bez konieczności stosowania dyrektywy #define (Chcę uniknąć brzydoty globalności danych!)

Każda pomoc jest mile widziana.

funt.
źródło
15
Dzięki za wszystkie twoje wspaniałe odpowiedzi! Niech żyje SO!
funt
Czy ktoś może mi powiedzieć, czym jest typ „integralny”? Dziękuję Ci bardzo.
funt
1
Typy całkowe oznaczają typy reprezentujące liczby całkowite. Zobacz publib.boulder.ibm.com/infocenter/comphelp/v8v101/...
bleater
Prywatny ciąg statyczny w twojej fabryce nie jest dobrym rozwiązaniem - weź pod uwagę, że klienci fabryki będą musieli wiedzieć, jakie kształty są obsługiwane, więc zamiast trzymać go w prywatnym statycznym, umieść je w osobnej przestrzeni nazw jako static const std :: string RECTANGLE = "Rectangle „.
LukeCodeBaker
jeśli twoja klasa jest klasą szablonową, zobacz stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Odpowiedzi:

471

Musisz zdefiniować element statyczny poza definicją klasy i podać tam inicjator.

Pierwszy

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

i wtedy

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

Składnia, której pierwotnie próbowałeś użyć (inicjator wewnątrz definicji klasy), jest dozwolona tylko w przypadku typów całkowych i wyliczeniowych.


Począwszy od C ++ 17 masz inną opcję, która jest dość podobna do twojej oryginalnej deklaracji: zmienne wbudowane

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

Nie jest wymagana dodatkowa definicja.

Lub zamiast tego constmożesz zadeklarować constexprw tym wariancie. Jawne inlinenie byłoby już konieczne, ponieważ constexprimplikuje inline.

Mrówka
źródło
8
Ponadto, jeśli nie ma wymogu używania ciągu STL, równie dobrze możesz po prostu zdefiniować const char *. (mniej kosztów ogólnych)
KSchmidt
50
Nie jestem pewien, czy zawsze jest to mniejszy narzut - zależy to od użytkowania. Jeśli ten element członkowski ma być przekazany jako argument funkcji, która przyjmuje stały ciąg znaków, dla każdego wywołania zostanie utworzony tymczasowo w stosunku do utworzenia jednego obiektu ciągu podczas inicjalizacji. Narzut IMHO związany z tworzeniem statycznego obiektu łańcuchowego jest pomijalny.
Tadeusz Kopec
23
Wolałbym też używać std :: string przez cały czas. Koszty ogólne są znikome, ale masz o wiele więcej opcji i znacznie mniej prawdopodobne jest, że napiszesz jakieś głupie rzeczy, takie jak „magia” == A :: PROSTOKĄT tylko po to, by porównać ich adres ...
Matthieu M.,
9
char const*ma dobroć, że jest inicjowana przed wszystkim inicjalizacji dynamiczna jest wykonywana. Zatem w konstruktorze dowolnego obiektu możesz polegać na tym, RECTANGLEże już został zainicjowany.
Johannes Schaub - litb
8
@cirosantilli: Ponieważ od samego początku w C ++ inicjalizatory były częścią definicji , a nie deklaracjami . A deklaracja elementu danych wewnątrz klasy jest po prostu: deklaracją. (Z drugiej strony zrobiono wyjątek dla stałych const i enum, a w C ++ 11 - dla stałych elementów literałów .)
AnT
153

W C ++ 11 możesz teraz:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
otchłań 7
źródło
30
Niestety to rozwiązanie nie działa dla std :: string.
HelloWorld,
2
Zauważ, że 1. działa to tylko z literałami i 2. nie jest to zgodne ze standardem, chociaż Gnu / GCC przestrzega grzywien, inne kompilatory zgłaszają błąd. Definicja musi być w ciele.
ManuelSchneid3r
2
@ ManuelSchneid3r Jak dokładnie jest to „niezgodne ze standardem”? Dla mnie wygląda to jak inicjalizacja nawiasów klamrowych lub równych C ++ 11 .
underscore_d
3
@rvighne, nie, to nieprawda. constexproznacza constzmienną, a nie typ it point. Tj. static constexpr const char* constJest taki sam jak static constexpr const char*, ale nie taki sam jak static constexpr char*.
midenok
2
@ abyss.7 - Dziękuję za odpowiedź, a ja mam jeszcze jedną: Dlaczego musi być statyczna?
Guy Avraham,
34

Wewnątrz definicji klas można deklarować tylko elementy statyczne. Muszą być zdefiniowane poza klasą. W przypadku stałych całkowania w czasie kompilacji standard stanowi wyjątek, że można „inicjować” elementy. Jednak wciąż nie jest to definicja. Na przykład adres nie działałby bez definicji.

Chciałbym wspomnieć, że nie widzę korzyści z używania std :: string nad const char [] dla stałych . std :: string jest niezły i wymaga dynamicznej inicjalizacji. Więc jeśli napiszesz coś takiego

const std::string foo = "hello";

w obszarze nazw konstruktor foo zostanie uruchomiony tuż przed wykonaniem startów głównych, a ten konstruktor utworzy kopię stałej „hello” w pamięci sterty. Chyba że naprawdę potrzebujesz RECTANGLE, aby był std :: string, możesz równie dobrze napisać

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

Tam! Bez alokacji sterty, bez kopiowania, bez dynamicznej inicjalizacji.

Pozdrawiam, s.

sellibitze
źródło
1
To jest odpowiedź przed C ++ 11. Użyj standardowego C ++ i użyj std :: string_view.
1
C ++ 11 nie ma widoku std :: string_view.
Lukas Salich,
17

To tylko dodatkowe informacje, ale jeśli naprawdę chcesz, aby ciąg znaków w pliku nagłówkowym, spróbuj czegoś takiego:

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

        return str;
    }
};

Chociaż wątpię, żeby to było zalecane.

GManNickG
źródło
To wygląda świetnie :) - domyślam się, że masz doświadczenie w innych językach niż c ++?
funt
5
Nie poleciłbym tego. Robię to często. Działa dobrze i uważam, że jest to bardziej oczywiste niż umieszczenie ciągu w pliku implementacji. Rzeczywiste dane std :: string są jednak nadal umieszczone na stercie. Zwróciłbym const char *, w którym to przypadku nie trzeba deklarować zmiennej statycznej, aby deklaracja zajmowała mniej miejsca (pod względem kodu). Ale to tylko kwestia gustu.
Zoomulator,
15

W C ++ 17 możesz używać zmiennych wbudowanych :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Zauważ, że różni się to od odpowiedzi w otchłani. 7 : Ta określa rzeczywisty std::stringobiekt, a nieconst char*

Oz Solomon
źródło
Czy nie sądzisz, że używanie inlineutworzy wiele duplikatów?
shuva,
1
@shuva Nie, zmienna nie zostanie zduplikowana .
zett42,
8

Aby zastosować tę klasową składnię inicjującą, stała musi być stałą statyczną typu całkowego lub typu wyliczenia zainicjowaną przez wyrażenie stałe.

To jest ograniczenie. Dlatego w tym przypadku musisz zdefiniować zmienną poza klasą. skieruj odpowiedź z @AndreyT

aJ.
źródło
7

Zmienne statyczne klasy można zadeklarować w nagłówku, ale muszą być zdefiniowane w pliku .cpp. Wynika to z faktu, że może istnieć tylko jedna instancja zmiennej statycznej, a kompilator nie może zdecydować, w którym wygenerowanym pliku obiektowym go umieścić, więc musisz podjąć decyzję.

Aby zachować definicję wartości statycznej z deklaracją w C ++ 11, można użyć zagnieżdżonej struktury statycznej. W takim przypadku element statyczny jest strukturą i musi zostać zdefiniowany w pliku .cpp, ale wartości znajdują się w nagłówku.

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

Zamiast inicjowania poszczególnych elementów cała struktura statyczna jest inicjowana w .cpp:

A::_Shapes A::shape;

Dostęp do wartości można uzyskać za pomocą

A::shape.RECTANGLE;

lub - ponieważ członkowie są prywatni i mają być używane tylko z A - z

shape.RECTANGLE;

Zauważ, że w tym rozwiązaniu nadal występuje problem kolejności inicjalizacji zmiennych statycznych. Gdy wartość statyczna jest używana do inicjalizacji innej zmiennej statycznej, pierwsza może jeszcze nie zostać zainicjowana.

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

W takim przypadku nagłówki zmiennych statycznych będą zawierać {""} lub {".h", ".hpp"}, w zależności od kolejności inicjowania utworzonej przez linker.

Jak wspomniano w @ abyss.7, można również użyć, constexprjeśli wartość zmiennej można obliczyć w czasie kompilacji. Ale jeśli zadeklarujesz swoje ciągi, static constexpr const char*a twój program użyje std::stringinaczej, powstanie narzut, ponieważ nowy std::stringobiekt będzie tworzony za każdym razem, gdy użyjesz takiej stałej:

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.
}
Marko Mahnič
źródło
Dobrze przygotowana odpowiedź Marko. Dwa szczegóły: jeden nie potrzebuje plików cpp dla członków klasy statycznej, a także proszę użyć std :: string_view dla dowolnego rodzaju stałych.
4

Obecny standard zezwala tylko na taką inicjalizację dla statycznych typów całek stałych. Musisz więc postępować zgodnie z wyjaśnieniem AndreyT. Będzie to jednak dostępne w następnym standardzie poprzez nową składnię inicjującą członka .

Leandro TC Melo
źródło
4

możliwe po prostu zrób:

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

lub

#define RECTANGLE "rectangle"
Chikuba
źródło
11
Używanie #define, gdy można użyć stałej maszynowej, jest po prostu błędne.
Artur Czajka
Twój pierwszy przykład jest w zasadzie dobrym rozwiązaniem, jeśli go nie masz, constexprale nie możesz wykonać funkcji statycznej const.
Frank Puffer
Tego rozwiązania należy unikać. Tworzy nowy ciąg przy każdym wywołaniu. Byłoby lepiej:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon
Dlaczego warto korzystać z pełnego kontenera jako wartości zwracanej? Użyj std :: string_vew .. jego zawartość pozostanie ważna w tym przypadku. jeszcze lepiej używaj literałów łańcuchowych, aby tworzyć i zwracać widok łańcucha ... i na koniec, ale nie mniej ważna, const zwracana wartość nie ma tutaj znaczenia ani wpływu .. tak, i ma to jako wstawienie, a nie statyczne, w nagłówku w nazwany obszar nazw ... i proszę, aby to był constexpr
4

Możesz albo wybrać const char*wyżej wspomniane rozwiązanie, ale jeśli potrzebujesz ciągów przez cały czas, będziesz miał dużo narzutów.
Z drugiej strony ciąg statyczny wymaga dynamicznej inicjalizacji, więc jeśli chcesz użyć jego wartości podczas inicjalizacji innej zmiennej globalnej / statycznej, możesz napotkać problem z kolejnością inicjalizacji. Aby tego uniknąć, najtańszą rzeczą jest dostęp do obiektu ciągu statycznego przez moduł pobierający, który sprawdza, czy obiekt został zainicjowany, czy nie.

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

Pamiętaj, aby używać tylko A::getS(). Ponieważ wszelkie wątki mogą być uruchamiane tylko main()i A_s_initializedinicjowane wcześniej main(), nie potrzebujesz blokad nawet w środowisku wielowątkowym. A_s_initializeddomyślnie jest 0 (przed dynamiczną inicjalizacją), więc jeśli używasz getS()przed inicjalizacją s, bezpiecznie wywołujesz funkcję init.

Btw, w odpowiedzi powyżej: „ static const std :: string RECTANGLE () const ”, funkcje statyczne nie mogą być, constponieważ nie mogą zmienić stanu, jeśli jakikolwiek obiekt (mimo to nie ma tego wskaźnika).

użytkownik2806882
źródło
4

Przewiń do 2018 i C ++ 17.

  • nie używaj std :: string, używaj literałów std :: string_view
  • zwróć uwagę na poniższy „constexpr”. Jest to również mechanizm „czasu kompilacji”.
  • brak inline nie oznacza powtórzeń
  • żadne pliki CPP nie są do tego potrzebne
  • static_assert „działa” tylko w czasie kompilacji

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

    };

Powyżej znajduje się odpowiedni i legalny obywatel C ++. Może łatwo zaangażować się we wszystkie algorytmy std ::, kontenery, narzędzia i tym podobne. Na przykład:

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

Ciesz się standardowym C ++


źródło
Używaj std::string_viewdla stałych tylko wtedy, gdy używasz string_viewparametrów we wszystkich swoich funkcjach. Jeśli którakolwiek z funkcji używa const std::string&parametru, kopia ciągu zostanie utworzona, gdy przekażesz string_viewstałą przez ten parametr. Jeśli twoje stałe są typu, std::stringkopie nie zostaną utworzone ani dla const std::string&parametrów, ani dla std::string_viewparametrów.
Marko Mahnič,
Ładna odpowiedź, ale ciekawa, dlaczego funkcja string_view jest zwracana z funkcji? Ten rodzaj sztuczki był przydatny, zanim inlinezmienne pojawiły się w C ++ 17 z ich semantyką ODR. Ale string_view to także C ++ 17, więc po prostu constexpr auto some_str = "compile time"sv;wykonuje to zadanie (a właściwie to nie jest zmienna, to jest constexpr, więc inlineniejawne; jeśli masz zmienną - tj. Nie constexpr- to inline auto some_str = "compile time"sv;zrobi to, chociaż oczywiście zakres przestrzeni nazw zmienna, która jest zasadniczo zmienną globalną, rzadko byłaby dobrym pomysłem).
Loss Mentality