Definiowanie statycznej stałej liczby całkowitej w definicji klasy

109

Rozumiem, że C ++ pozwala na definiowanie statycznych elementów stałych wewnątrz klasy, o ile jest to typ całkowity.

Dlaczego więc poniższy kod powoduje błąd konsolidatora?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Pojawia się błąd:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Co ciekawe, jeśli wykomentuję wywołanie std :: min, kod kompiluje się i łączy w porządku (mimo że odwołanie do testu :: N również znajduje się w poprzednim wierszu).

Masz jakiś pomysł, co się dzieje?

Mój kompilator to gcc 4.4 w systemie Linux.

HighCommander4
źródło
3
Działa dobrze na Visual Studio 2010.
Puppy
4
Ten dokładny błąd jest wyjaśniony na stronie gcc.gnu.org/wiki/ ...
Jonathan Wakely
W konkretnym przypadku charmożesz zdefiniować go jako constexpr static const char &N = "n"[0];. Zwróć uwagę na &. Myślę, że to działa, ponieważ ciągi literałów są definiowane automatycznie. Martwię się tym - może to zachowywać się dziwnie w pliku nagłówkowym między różnymi jednostkami tłumaczeniowymi, ponieważ łańcuch prawdopodobnie będzie znajdować się pod wieloma różnymi adresami.
Aaron McDaid
1
To pytanie jest manifestacją tego, jak słaba jest nadal odpowiedź C ++ na „nie używaj #defines dla stałych”.
Johannes Overmann
1
@JohannesOvermann W związku z tym chciałbym wspomnieć o użyciu inline dla zmiennych globalnych od C ++ 17 inline const int N = 10, który według mojej wiedzy nadal ma magazyn zdefiniowany przez linker. W tym przypadku można również użyć słowa kluczowego wbudowanego, aby podać definicję zmiennej statycznej w teście definicji klasy.
Wormer

Odpowiedzi:

72

Rozumiem, że C ++ pozwala na definiowanie statycznych elementów stałych wewnątrz klasy, o ile jest to typ całkowity.

Masz trochę racji. Możesz inicjować statyczne całki stałe w deklaracji klasy, ale to nie jest definicja.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Co ciekawe, jeśli wykomentuję wywołanie std :: min, kod kompiluje się i łączy w porządku (mimo że odwołanie do testu :: N również znajduje się w poprzednim wierszu).

Masz jakiś pomysł, co się dzieje?

std :: min przyjmuje parametry przez odwołanie do stałej. Gdyby wziąć je według wartości, nie miałbyś tego problemu, ale ponieważ potrzebujesz odniesienia, potrzebujesz również definicji.

Oto rozdział / werset:

9.4.2 / 4 - Jeśli element staticczłonkowski danych jest typu constcałkowego lub constwyliczeniowego, jego deklaracja w definicji klasy może określać inicjalizator stałej, który powinien być całkowym wyrażeniem stałym (5.19). W takim przypadku element członkowski może pojawić się w całkowitych wyrażeniach stałych. Element członkowski powinien być nadal zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie, a definicja zakresu przestrzeni nazw nie powinna zawierać inicjatora .

Zobacz odpowiedź Chu, aby uzyskać możliwe obejście.

Edward Strange
źródło
Rozumiem, to interesujące. W takim przypadku jaka jest różnica między podaniem wartości w miejscu deklaracji a podaniem wartości w momencie definicji? Który jest zalecany?
HighCommander4
Cóż, uważam, że bez definicji można się obejść bez definicji, o ile w rzeczywistości nie „użyje się” zmiennej. Jeśli używasz go tylko jako części wyrażenia stałego, zmienna nigdy nie jest używana. W przeciwnym razie nie wydaje się, aby istniała duża różnica, poza możliwością zobaczenia wartości w nagłówku - co może być tym, czego chcesz, ale nie musi.
Edward Strange
2
Krótka odpowiedź to stała statyczna x = 1; jest wartością r, ale nie jest wartością l. Wartość jest dostępna jako stała w czasie kompilacji (można przy jej pomocy zwymiarować tablicę) static const y; [no initializer] musi być zdefiniowane w pliku cpp i może być używane jako rvalue lub lvalue.
Dale Wilson,
2
Byłoby miło, gdyby mogli to rozszerzyć / ulepszyć. obiekty zainicjalizowane, ale niezdefiniowane, należy moim zdaniem traktować tak samo jak literały. Na przykład wolno nam przypisać literał 5do a const int&. Dlaczego więc nie traktować PO test::Njako odpowiedniego dosłownego?
Aaron McDaid
Ciekawe wyjaśnienie, dzięki! Oznacza to, że w C ++ static const int nadal nie zastępuje liczby całkowitej #defines. enum jest zawsze podpisane tylko int, więc należy używać klas wyliczeniowych dla poszczególnych stałych. Byłoby dla mnie całkiem oczywiste, aby zdegenerować stałą deklarację ze stałymi i znanymi wartościami na stałą dosłowną, w jaki sposób można to skompilować bez problemów. C ++ ma przed sobą długą drogę ...
Johannes Overmann
51

Przykład Bjarne Stroustrupa w jego C ++ FAQ sugeruje, że masz rację i potrzebujesz definicji tylko wtedy, gdy bierzesz adres.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Mówi: „Możesz wziąć adres statycznego elementu członkowskiego, jeśli (i tylko wtedy) ma on definicję spoza klasy” . Co sugeruje, że działałoby inaczej. Może twoja funkcja min wywołuje adresy zza kulis.

HostileFork mówi, że nie ufaj SE
źródło
2
std::minprzyjmuje parametry przez odniesienie, dlatego wymagana jest definicja.
Rakete1111
Jak napisać definicję, jeśli AE jest klasą szablonu AE <class T>, a c7 nie jest int, ale T :: size_type? Mam wartość zainicjowaną na „-1” w nagłówku, ale clang mówi o niezdefiniowanej wartości i nie wiem, jak napisać definicję.
Fabian
@Fabian Jestem w podróży, rozmawiam przez telefon i jestem trochę zajęty ... ale myślę, że twój komentarz brzmi tak, jakby najlepiej był napisać go jako nowe pytanie. Napisz MCVE łącznie z otrzymanym błędem, może też dorzuć to, co mówi gcc. Założę się, że ludzie szybko powiedzieliby, co jest.
HostileFork mówi, że nie ufaj SE
@HostileFork: Pisząc MCVE, czasami sam wymyślasz rozwiązanie. W moim przypadku odpowiedź brzmi, template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;gdzie KeyContainer jest typedef z std :: vector <K>. Należy wymienić wszystkie parametry szablonu i wpisać nazwę typu, ponieważ jest to typ zależny. Może komuś ten komentarz przyda się. Jednak teraz zastanawiam się, jak wyeksportować to w bibliotece DLL, ponieważ klasa szablonu jest oczywiście w nagłówku. Czy muszę wyeksportować c7 ???
Fabian
24

Innym sposobem, aby to zrobić, w każdym razie w przypadku typów całkowitych, jest zdefiniowanie stałych jako wyliczeń w klasie:

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
źródło
2
A to prawdopodobnie rozwiązałoby problem. Kiedy N jest używane jako parametr dla min (), spowoduje to utworzenie tymczasowego zamiast próby odniesienia się do rzekomo istniejącej zmiennej.
Edward Strange
Miało to tę zaletę, że można go uczynić prywatnym.
Agostino,
11

Nie tylko int. Ale nie możesz zdefiniować wartości w deklaracji klasy. Jeśli masz:

class classname
{
    public:
       static int const N;
}

w pliku .h musisz mieć:

int const classname::N = 10;

w pliku .cpp.

Amardeep AC9MF
źródło
2
Zdaję sobie sprawę, że w deklaracji klasy można zadeklarować zmienną dowolnego typu. Powiedziałem, że myślę, że statyczne stałe całkowite można również zdefiniować w deklaracji klasy. Czy tak nie jest? Jeśli nie, dlaczego kompilator nie wyświetla błędu w wierszu, w którym próbuję go zdefiniować w klasie? Co więcej, dlaczego linia std :: cout nie powoduje błędu konsolidatora, a linia std :: min tak?
HighCommander4
Nie, nie można zdefiniować statycznych elementów członkowskich w deklaracji klasy, ponieważ inicjalizacja emituje kod. W przeciwieństwie do funkcji wbudowanej, która również emituje kod, definicja statyczna jest unikalna w skali globalnej.
Amardeep AC9MF,
@ HighCommander4: Możesz podać inicjalizator dla static constintegralnego elementu członkowskiego w definicji klasy. Ale to wciąż nie definiuje tego członka. Zobacz odpowiedź Noah Robertsa po szczegóły.
AnT,
9

Oto inny sposób obejścia tego problemu:

std::min(9, int(test::N));

(Myślę, że odpowiedź Szalonego Eddiego poprawnie opisuje, dlaczego problem istnieje).

karadoc
źródło
5
lub nawetstd::min(9, +test::N);
Tomilov Anatoliy
Oto jednak główne pytanie: czy to wszystko jest optymalne? Nie wiem jak wy, ale moją wielką atrakcją w pomijaniu definicji jest to, że nie powinno zajmować żadnej pamięci ani narzutów przy używaniu statycznej const.
Opux
6

Od C ++ 11 możesz używać:

static constexpr int N = 10;

Teoretycznie nadal wymaga to zdefiniowania stałej w pliku .cpp, ale dopóki nie weźmiesz jej adresu N, jest bardzo mało prawdopodobne, że jakakolwiek implementacja kompilatora spowoduje błąd;).

Carlo Wood
źródło
A co, jeśli chcesz przekazać wartość jako argument typu „const int &”, jak w przykładzie? :-)
Wormer
To działa dobrze. Nie tworzysz instancji N w ten sposób, po prostu przekazujesz stałą referencję do tymczasowej. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood
Może C ++ 17, nie C ++ 14, a nawet nie C ++ 17 we wcześniejszych wersjach gcc 6.3.0 i starszych, to nie jest standardowa rzecz. Ale dzięki za wspomnienie o tym.
Wormer
Ach tak, masz rację. Nie próbowałem C ++ 14 na Wandbox. No cóż, to jest ta część, w której powiedziałem: „To teoretycznie nadal wymaga zdefiniowania stałej”. Masz więc rację, że nie jest to „standard”.
Carlo Wood
3

C ++ umożliwia definiowanie statycznych składowych stałych wewnątrz klasy

Nie, 3.1 §2 mówi:

Deklaracja jest definicją, chyba że deklaruje funkcję bez określenia treści funkcji (8.4), zawiera specyfikator extern (7.1.1) lub specyfikację powiązania (7.5) i ani inicjalizator, ani element funkcji, deklaruje dane statyczne element członkowski w definicji klasy (9.4), jest to deklaracja nazwy klasy (9.1), jest to nieprzezroczysta-deklaracja-wyliczenia (7.2) lub jest to deklaracja typu typedef (7.1.3), deklaracja-use (7.3.1). 3) lub dyrektywę using (7.3.4).

fredoverflow
źródło