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.


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.


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.


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

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.


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


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


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

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.