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.


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.


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

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.


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.


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,


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


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

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

możliwe po prostu zrób:

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

lub

#define RECTANGLE "rectangle"

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


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 ++


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.