class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
Nie widzę potrzeby A::x
osobnego definiowania w pliku .cpp (lub w tym samym pliku dla szablonów). Dlaczego nie można A::x
zadeklarować i zdefiniować jednocześnie?
Czy zostało to zabronione z powodów historycznych?
Moje główne pytanie brzmi: czy wpłynie to na jakąkolwiek funkcjonalność, jeśli static
elementy danych zostały zadeklarowane / zdefiniowane w tym samym czasie (tak samo jak Java )?
c++
data
language-features
grammar
static-access
iammilind
źródło
źródło
inline static int x[] = {1, 2, 3};
. Zobacz en.cppreference.com/w/cpp/language/static#Static_data_membersOdpowiedzi:
Myślę, że rozważane ograniczenie nie jest związane z semantyką (dlaczego coś miałoby się zmieniać, gdyby inicjalizacja została zdefiniowana w tym samym pliku?), Ale raczej z modelem kompilacji C ++, którego ze względu na kompatybilność wsteczną nie można łatwo zmienić, ponieważ albo stają się zbyt skomplikowane (obsługując jednocześnie nowy model kompilacji i istniejący) lub nie pozwalają na kompilację istniejącego kodu (poprzez wprowadzenie nowego modelu kompilacji i usunięcie istniejącego).
Model kompilacji C ++ wywodzi się z modelu C, w którym importujesz deklaracje do pliku źródłowego, włączając pliki (nagłówkowe). W ten sposób kompilator widzi dokładnie jeden duży plik źródłowy, zawierający wszystkie zawarte pliki i wszystkie pliki z tych plików, rekurencyjnie. Ma to jedną wielką zaletę IMO, a mianowicie to, że ułatwia wdrożenie kompilatora. Oczywiście w zapisanych plikach można pisać wszystko, tzn. Deklaracje i definicje. Dobrą praktyką jest umieszczanie deklaracji w plikach nagłówkowych i definicji w plikach .c lub .cpp.
Z drugiej strony możliwe jest posiadanie modelu kompilacji, w którym kompilator doskonale wie, czy importuje deklarację symbolu globalnego zdefiniowanego w innym module , czy też kompiluje definicję symbolu globalnego dostarczoną przez obecny moduł . Tylko w tym drugim przypadku kompilator musi umieścić ten symbol (np. Zmienną) w bieżącym pliku obiektowym.
Na przykład w GNU Pascal możesz zapisać jednostkę
a
w plikua.pas
takim jak ten:gdzie zmienna globalna jest zadeklarowana i zainicjowana w tym samym pliku źródłowym.
Następnie możesz mieć różne jednostki, które importują a i używają zmiennej globalnej
MyStaticVariable
, np. Jednostkę b (b.pas
):oraz jednostka c (
c.pas
):Wreszcie możesz użyć jednostek b i c w głównym programie
m.pas
:Możesz skompilować te pliki osobno:
a następnie utworzyć plik wykonywalny z:
i uruchom to:
Sztuczka polega na tym, że gdy kompilator widzi dyrektywę use w module programu (np. Używa a w b.pas), nie zawiera odpowiedniego pliku .pas, ale szuka pliku .gpi, czyli wstępnie skompilowanej plik interfejsu (patrz dokumentacja ).
.gpi
Pliki te są generowane przez kompilator wraz z.o
plikami podczas kompilacji każdego modułu. Tak więc symbol globalnyMyStaticVariable
jest zdefiniowany tylko raz w pliku obiektowyma.o
.Java działa w podobny sposób: kiedy następnie kompilator importuje klasę A do klasy B, szuka pliku klasy dla A i nie potrzebuje go
A.java
. Tak więc wszystkie definicje i inicjalizacje dla klasy A można umieścić w jednym pliku źródłowym.Wracając do C ++, powód, dla którego w C ++ musisz zdefiniować statyczne elementy danych w osobnym pliku, jest bardziej związany z modelem kompilacji C ++ niż z ograniczeniami narzuconymi przez linker lub inne narzędzia używane przez kompilator. W C ++ import niektórych symboli oznacza zbudowanie ich deklaracji jako części bieżącej jednostki kompilacji. Jest to bardzo ważne między innymi ze względu na sposób kompilowania szablonów. Oznacza to jednak, że nie można / nie należy definiować żadnych globalnych symboli (funkcji, zmiennych, metod, elementów danych statycznych) w dołączonym pliku, w przeciwnym razie symbole te mogłyby zostać wielokrotnie zdefiniowane w skompilowanych plikach obiektowych.
źródło
Ponieważ elementy statyczne są współużytkowane przez WSZYSTKIE instancje klasy, muszą być zdefiniowane w jednym i tylko jednym miejscu. Naprawdę, są to zmienne globalne z pewnymi ograniczeniami dostępu.
Jeśli spróbujesz zdefiniować je w nagłówku, zostaną one zdefiniowane w każdym module, który zawiera ten nagłówek, a podczas łączenia wystąpią błędy podczas wyszukiwania wszystkich duplikatów definicji.
Tak, przynajmniej częściowo jest to kwestia historyczna pochodząca z frontu; mógłby zostać napisany kompilator, który stworzyłby rodzaj ukrytego „static_members_of_everything.cpp” i link do tego. Jednak złamałoby to kompatybilność wsteczną i nie przyniosłoby to żadnej realnej korzyści.
źródło
static
zmienne są zadeklarowane / zdefiniowane w tym samym miejscu (jak Java), to co może pójść nie tak?static
członkamitemplate
? Są dozwolone we wszystkich plikach nagłówka, ponieważ muszą być widoczne. Nie kwestionuję tej odpowiedzi, ale nie pasuje ona również do mojego pytania.Prawdopodobnym powodem jest to, że dzięki temu język C ++ można wdrożyć w środowiskach, w których plik obiektowy i model powiązań nie obsługują łączenia wielu definicji z wielu plików obiektowych.
Deklaracja klasy (zwana deklaracją z ważnych powodów) zostaje wciągnięta do wielu jednostek tłumaczeniowych. Jeśli deklaracja zawiera definicje zmiennych statycznych, wówczas otrzymujesz wiele definicji w wielu jednostkach tłumaczeniowych (i pamiętaj, że te nazwy mają powiązanie zewnętrzne).
Taka sytuacja jest możliwa, ale wymaga od linkera obsługi wielu definicji bez narzekania.
(Należy pamiętać, że jest to sprzeczne z regułą jednej definicji, chyba że można tego dokonać zgodnie z rodzajem symbolu lub sekcją, w której jest umieszczony).
źródło
Istnieje ogromna różnica między C ++ a Javą.
Java działa na własnej maszynie wirtualnej, która tworzy wszystko we własnym środowisku wykonawczym. Jeśli definicja zostanie wyświetlona więcej niż jeden raz, po prostu zadziała na ten sam obiekt, o którym środowisko wykonawcze wie najlepiej.
W C ++ nie ma „ostatecznego właściciela wiedzy”: C ++, C, Fortran Pascal itp. Są „tłumaczami” z kodu źródłowego (pliku CPP) na format pośredni (plik OBJ lub plik „.o”, w zależności od OS), w którym instrukcje są tłumaczone na instrukcje maszynowe, a nazwy stają się adresami pośrednimi, w których pośredniczy tablica symboli.
Program nie jest tworzony przez kompilator, ale przez inny program („linker”), który łączy wszystkie OBJ-y razem (bez względu na język, z którego pochodzą) poprzez przekierowanie wszystkich adresów skierowanych w stronę symboli, w kierunku ich skuteczna definicja.
Przy tym, jak działa linker, definicja (która tworzy fizyczną przestrzeń dla zmiennej) musi być unikalna.
Zauważ, że C ++ sam w sobie nie łączy, a linker nie jest wydawany przez specyfikacje C ++: linker istnieje ze względu na sposób budowania modułów systemu operacyjnego (zwykle w C i ASM). C ++ musi go używać tak, jak jest.
Teraz: plik nagłówka można „wkleić” do kilku plików CPP. Każdy plik CPP jest tłumaczony niezależnie od każdego innego. Kompilator tłumaczący różne pliki CPP, wszystkie odbierające w tej samej definicji umieści „ kod tworzenia ” dla zdefiniowanego obiektu we wszystkich wynikowych OBJ.
Kompilator nie wie (i nigdy nie będzie wiedział), czy wszystkie te OBJ zostaną kiedykolwiek użyte razem do utworzenia jednego programu lub osobno do utworzenia różnych niezależnych programów.
Linker nie wie, w jaki sposób i dlaczego istnieją definicje i skąd pochodzą (nawet nie wie o C ++: każdy „statyczny język” może tworzyć definicje i odniesienia do połączenia). Po prostu wie, że istnieją odniesienia do danego „symbolu”, który jest „zdefiniowany” pod danym adresem wynikowym.
Jeśli dla danego symbolu istnieje wiele definicji (nie myl definicji z odniesieniami), linker nie ma wiedzy (bez względu na język) na temat tego, co z nimi zrobić.
To tak, jakby połączyć kilka miast i stworzyć duże miasto: jeśli okaże się, że masz dwa „ Plac Czasu ” i pewną liczbę osób przybywających z zewnątrz, proszących o przejście na „ Plac Czasu ”, nie możesz zdecydować na podstawie czysto technicznej (bez wiedzy na temat polityki, która przypisała te nazwiska i będzie odpowiedzialna za zarządzanie nimi), w którym dokładnym miejscu je wysłać.
źródło
Jest to wymagane, ponieważ w przeciwnym razie kompilator nie wie, gdzie umieścić zmienną. Każdy plik CPP jest indywidualnie kompilowany i nie wie o drugim. Linker rozwiązuje zmienne, funkcje itp. Osobiście nie widzę różnicy między elementami vtable i statycznymi (nie musimy wybierać, w jakim pliku są zdefiniowane vtable).
W większości zakładam, że pisarzom kompilatorów łatwiej jest to zaimplementować. Istnieją zmienne statyczne poza klasą / strukturą i być może albo ze względu na spójność, albo dlatego, że dla twórców kompilatorów łatwiej byłoby je zaimplementować, zdefiniowali to ograniczenie w standardach.
źródło
Myślę, że znalazłem powód. Zdefiniowanie
static
zmiennej w oddzielnej przestrzeni pozwala na zainicjowanie jej do dowolnej wartości. Jeśli nie zostanie zainicjowany, domyślnie zostanie ustawiony na 0.Przed C ++ 11 inicjalizacja w klasie była niedozwolona w C ++. Nie można więc pisać w następujący sposób:
Dlatego teraz, aby zainicjować zmienną, należy zapisać ją poza klasą jako:
Jak omówiono również w innych odpowiedziach,
int X::i
jest teraz globalny, a deklarowanie globalności w wielu plikach powoduje błąd wielu łączy symboli.Dlatego należy zadeklarować
static
zmienną klasy w oddzielnej jednostce tłumaczenia. Jednak nadal można argumentować, że następujący sposób powinien poinstruować kompilator, aby nie tworzył wielu symboliźródło
A :: x jest tylko zmienną globalną, ale przestrzeń nazw ma A i ma ograniczenia dostępu.
Ktoś nadal musi to zadeklarować, jak każda inna zmienna globalna, a można to zrobić nawet w projekcie, który jest statycznie powiązany z projektem zawierającym resztę kodu A.
Nazwałbym to wszystko złym projektem, ale istnieje kilka funkcji, które można wykorzystać w ten sposób:
kolejność wywołań konstruktora ... Nie ważne dla int, ale dla bardziej złożonego elementu, który może uzyskiwać dostęp do innych zmiennych statycznych lub globalnych, może być krytyczny.
inicjalizator statyczny - możesz pozwolić klientowi zdecydować, na co należy zainicjować A :: x.
w c ++ i c, ponieważ masz pełny dostęp do pamięci za pomocą wskaźników, fizyczna lokalizacja zmiennych jest znacząca. Są bardzo niegrzeczne rzeczy, które można wykorzystać w zależności od tego, gdzie zmienna znajduje się w obiekcie łącza.
Wątpię, by to „dlaczego” zaistniała taka sytuacja. Prawdopodobnie jest to tylko ewolucja C zmieniającej się w C ++ i problem kompatybilności wstecznej, który powstrzymuje cię przed zmianą języka.
źródło