2008-11-07 11 views
172

Czy ktoś może wyjaśnić, dlaczego poniższy kod nie zostanie skompilowany? Przynajmniej na g ++ 4.2.4.Niezdefiniowane odniesienie do statycznego członka klasy

I bardziej interesujące, dlaczego skompiluje się, gdy rzucam MEMBER do 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; 
} 
+0

Edytowałem pytanie, aby wprowadzić wcięcie kodu przez cztery spacje zamiast używać

. Oznacza to, że nawiasy kątowe nie są interpretowane jako HTML. –

+0

Wiwaty :) Reguła 10 znaków jest czasami denerwująca;) –

Odpowiedz

176

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

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

const int Foo::MEMBER; 

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

To powinno pozbyć się niezdefiniowanej referencji.

+3

Dobra inicjacja inline liczb całkowitych stałych tworzy stałą całkowitą o zakresie, której nie można wziąć z adresu, a wektor przyjmuje parametr odniesienia. –

+9

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 zewnętrznej deklaracji? – nobar

+27

Po prostu spędziłem sporo czasu, stwierdzając, że jeśli definicja klasy znajduje się w pliku nagłówkowym, to przydział zmiennej statycznej powinien znajdować się w pliku implementacji, a nie w nagłówku. – shanet

69

Problem pojawia się z powodu ciekawego zderzenia nowych funkcji C++ i tego, co próbujesz zrobić. Najpierw rzućmy okiem na podpis push_back:

void push_back(const T&) 

To spodziewa się odniesienie do obiektu typu T. W starym systemie inicjalizacji taki członek istnieje. Na przykład, następujący kod kompiluje 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; 
} 

To dlatego, że istnieje rzeczywisty obiekt gdzieś, że ma tę wartość przechowywaną w nim. Jeśli jednak przełączysz się na nową metodę określania stałych elementów stałych, tak jak powyżej, Foo::MEMBER nie jest już obiektem. Jest to stała, nieco podobny do:

#define MEMBER 1 

ale bez głowy makra preprocesora (oraz z bezpieczeństwem typu). Oznacza to, że wektor, który oczekuje odniesienia, nie może go uzyskać.

+1

To daje pełny sens –

+1

dzięki, że pomogło ... to może kwalifikować się do http://stackoverflow.com/questions/1995113/strangest-language-feature jeśli go tam już nie ma ... –

+1

Warto również zauważyć, że MSVC akceptuje wersję bez obsady bez skarg. – porges

1

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

+0

Myślę, że odpowiadasz na własne pytanie: obsada działa, ponieważ tworzy (tymczasowe) odniesienie. –

56

Standard C++ wymaga definicji dla statycznego członka stałego, jeśli definicja jest w jakiś sposób potrzebna.

Definicja jest wymagana, na przykład, jeśli jest używany adres. push_back bierze swój parametr przez odniesienie do const, więc ściśle kompilator potrzebuje adresu twojego członka i musisz go zdefiniować w przestrzeni nazw.

Kiedy wyraźnie rzucasz stałą, tworzysz tymczasowy i jest to tymczasowy, który jest związany z odwołaniem (zgodnie ze specjalnymi regułami w standardzie).

To naprawdę ciekawy przypadek, a ja naprawdę myślę, że warto zgłosić problem, aby std mógł zostać zmieniony tak, by miał takie samo zachowanie dla stałego członka!

Chociaż w dziwny sposób można to postrzegać jako uzasadnione użycie operatora jednoargumentowego "+". Zasadniczo wynikiem unary + jest rvalue a więc zasady dla wiązania rvalues ​​do const zastosować referencje i nie używamy adres naszej statycznej członka const:

v.push_back(+Foo::MEMBER); 
+2

+1. Tak, to na pewno dziwne, że dla obiektu x typu T wyrażenie "(T) x" może być użyte do wiązania stałej wartości stałej, podczas gdy zwykłe "x" nie może. Uwielbiam twoją obserwację na temat "unary +"! Kto by pomyślał, że biedny mały "unary +" faktycznie miał zastosowanie ... :) –

+3

Myślenie o ogólnym przypadku ... Czy istnieje inny typ obiektu w C++, który ma właściwość, że to (1) może być używane jako lwartość tylko wtedy, gdy została zdefiniowana, ale (2) może zostać przekonwertowana na wartość rdzenia bez zdefiniowania? –

+0

Dobre pytanie, a przynajmniej w tej chwili nie mogę wymyślić żadnych innych przykładów. Jest to prawdopodobnie tylko tutaj, ponieważ komitet głównie wykorzystywał istniejącą składnię. –

9

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

+1 "Krótka odpowiedź" – dariush

0

Odnośnie drugiego pytania: push_ref bierze odniesienie jako parametr, a nie można mieć odniesienie do statycznej const Członek Rady klasy/struktury. Po wywołaniu static_cast tworzona jest zmienna tymczasowa. I odniesienie do tego obiektu można przekazać, wszystko działa dobrze.

Albo przynajmniej mój kolega, który to rozstrzygnął, tak powiedział.

1

z C++ 11, powyższa byłaby możliwa do podstawowych typów jak

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

constexpr część tworzy statyczne wyraz w przeciwieństwie do statycznego zmiennej - i że zachowuje się tak samo jak u wyjątkowo prosta definicja metody inline. Podejście to okazało się nieco chwiejne ze względu na ciągi znaków C-string wewnątrz klas szablonów.

Powiązane problemy