Niezdefiniowane odniesienie do członka klasy statycznej

201

Czy ktoś może wyjaśnić, dlaczego następujący kod nie zostanie skompilowany? Przynajmniej na g ++ 4.2.4.

A co ciekawsze, dlaczego będzie się kompilować, gdy obsadzę MEMBER na int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}
Paweł Piatkowski
źródło
Zredagowałem pytanie, aby wciąć kod o cztery spacje zamiast używać <pre> <code> </code> </pre>. Oznacza to, że nawiasy kątowe nie są interpretowane jako HTML.
Steve Jessop,
stackoverflow.com/questions/16284629/... Możesz odnieść się do tego pytania.
Tooba Iqbal

Odpowiedzi:

199

Musisz gdzieś zdefiniować element statyczny (po definicji klasy). Spróbuj tego:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

To powinno pozbyć się niezdefiniowanego odniesienia.

Drew Hall
źródło
3
Dobrze, że inicjalizacja wbudowanej stałej statycznej liczby całkowitej tworzy stałą liczbową o zasięgu, której nie można przyjąć adresu, a wektor przyjmuje parametr odniesienia.
Evan Teran,
10
Ta odpowiedź dotyczy tylko pierwszej części pytania. Druga część jest o wiele bardziej interesująca: dlaczego dodanie obsady NOP sprawia, że ​​działa ona bez wymagania deklaracji zewnętrznej?
nobar
32
Spędziłem sporo czasu, zastanawiając się, czy jeśli definicja klasy znajduje się w pliku nagłówkowym, wówczas przydział zmiennej statycznej powinien znajdować się w pliku implementacyjnym, a nie w nagłówku.
shanet
1
@shanet: Bardzo dobra uwaga - powinienem wspomnieć o tym w mojej odpowiedzi!
Drew Hall
Ale jeśli zadeklaruję to jako const, czy nie mogę zmienić wartości tej zmiennej?
Namratha
78

Problem pojawia się z powodu interesującego zderzenia nowych funkcji C ++ i tego, co próbujesz zrobić. Najpierw spójrzmy na push_backpodpis:

void push_back(const T&)

Oczekuje odwołania do obiektu typu T. W starym systemie inicjalizacji taki element istnieje. Na przykład następujący kod kompiluje się dobrze:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Wynika to z faktu, że gdzieś istnieje obiekt, w którym jest zapisana ta wartość. Jeśli jednak przejdziesz na nową metodę określania stałych stałych statycznych, tak jak masz powyżej, Foo::MEMBERnie będzie już obiektem. Jest to stała, nieco podobna do:

#define MEMBER 1

Ale bez problemów z makrem preprocesora (i z bezpieczeństwem typu). Oznacza to, że wektor, który oczekuje odniesienia, nie może go otrzymać.

Douglas Mayle
źródło
2
dzięki, które pomogły ... które mogłyby się zakwalifikować do stackoverflow.com/questions/1995113/strangest-language-feature, jeśli jeszcze go nie ma ...
Andre Holzner
1
Warto również zauważyć, że MSVC akceptuje wersję nie obsadzoną bez reklamacji.
porges
4
-1: To po prostu nieprawda. Nadal należy definiować elementy statyczne inicjowane inline, gdy są one gdzieś używane odr . To, że optymalizacje kompilatora mogą pozbyć się błędu linkera, nie zmienia tego. W tym przypadku konwersja wartości z wartości na wartość (dzięki (int)rzutowaniu) zachodzi w jednostce tłumaczenia z doskonałą widocznością stałej i Foo::MEMBERnie jest już używana odr . Jest to w przeciwieństwie do pierwszego wywołania funkcji, w którym odniesienie jest przekazywane i oceniane w innym miejscu.
Wyścigi lekkości na orbicie
Co void push_back( const T& value );? const&może wiązać się z wartościami.
Kostas
59

Standard C ++ wymaga definicji statycznego elementu const, jeśli definicja jest w jakiś sposób potrzebna.

Definicja jest wymagana, na przykład jeśli używany jest jej adres. push_backprzyjmuje parametr przez odniesienie do stałej, więc kompilator ściśle potrzebuje adresu członka i należy go zdefiniować w przestrzeni nazw.

Kiedy jawnie rzutujesz stałą, tworzysz tymczasowy i jest to tymczasowy, który jest powiązany z referencją (na specjalnych zasadach w standardzie).

To naprawdę interesujący przypadek i naprawdę uważam, że warto poruszyć problem, aby zmienić standardowe std, aby zachować takie samo zachowanie dla stałego członka!

Chociaż w dziwny sposób można to postrzegać jako uzasadnione użycie jednoargumentowego operatora „+”. Zasadniczo wynikiem unary +jest wartość, dlatego obowiązują zasady wiązania wartości z referenami const i nie używamy adresu naszego statycznego członka const:

v.push_back( +Foo::MEMBER );
Richard Corden
źródło
3
+1. Tak, to z pewnością dziwne, że dla obiektu x typu T wyrażenie „(T) x” może być użyte do powiązania stałej referencji, podczas gdy zwykłe „x” nie. Podoba mi się twoja obserwacja na temat „unary +”! Kto by pomyślał, że ten biedny mały „unary +” rzeczywiście
przydał się
3
Myśląc o ogólnym przypadku ... Czy jest jakiś inny typ obiektu w C ++, który ma właściwość, że (1) może być użyty jako wartość tylko wtedy, gdy został zdefiniowany, ale (2) może być przekształcony w wartość bez konieczności zdefiniowane?
j_random_hacker
Dobre pytanie, a przynajmniej w tej chwili nie mogę wymyślić żadnych innych przykładów. Jest to prawdopodobnie tylko tutaj, ponieważ komitet przeważnie tylko używał istniejącej składni.
Richard Corden
@RichardCorden: jak to rozwiązało jednoargumentowe +?
Blood-HaZaRd
1
@ Blood-HaZaRd: Przed odniesieniami do wartości jedynym przeciążeniem push_backbyło const &. Bezpośrednie użycie członka spowodowało, że członek został powiązany z referencją, co wymagało posiadania adresu. Jednak dodanie +tworzy tymczasowy z wartością członka. Odwołanie następnie wiąże się z tym tymczasowym, zamiast wymagać od członka posiadania adresu.
Richard Corden,
10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;
iso9660
źródło
1

Nie mam pojęcia, dlaczego obsada działa, ale Foo :: MEMBER nie jest przydzielany do pierwszego załadowania Foo, a ponieważ nigdy go nie ładujesz, nigdy nie jest przydzielany. Gdybyś miał gdzieś odniesienie do Foo, prawdopodobnie by to zadziałało.

Paul Tomblin
źródło
Myślę, że odpowiadasz na własne pytanie: obsada działa, ponieważ tworzy (tymczasowe) odwołanie.
Jaap Versteegh
1

W przypadku C ++ 11 powyższe byłoby możliwe dla podstawowych typów as

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

constexprCzęść tworzy statyczny wyraz w przeciwieństwie do statycznej zmiennej - i która zachowuje się jak bardzo prostej definicji metody inline. Podejście to okazało się jednak nieco chwiejne w przypadku ciągów C-string wewnątrz klas szablonów.

zaskoczyć
źródło
Okazało się to dla mnie niezbędne, ponieważ „static const int MEMBER = 1;” jest potrzebny do użycia MEMBER w przełącznikach, podczas gdy deklaracja zewnętrzna jest potrzebna do użycia go w wektorach i nie można mieć obu jednocześnie. Ale wyrażenie dajesz tu robi praca dla obu, przynajmniej z mojego kompilatora.
Ben Farmer,
0

Odnośnie drugiego pytania: push_ref przyjmuje odniesienie jako parametr i nie możesz mieć odniesienia do statycznego elementu pamięci klasy / struktury. Po wywołaniu static_cast tworzona jest zmienna tymczasowa. Odniesienie do tego obiektu można przekazać, wszystko działa dobrze.

A przynajmniej tak powiedział mój kolega, który to rozwiązał.

Quarra
źródło