Szukam definicji, kiedy wolno mi wykonać deklarację przekazywania klasy w pliku nagłówkowym innej klasy:
Czy mogę to zrobić dla klasy podstawowej, dla klasy będącej członkiem, dla klasy przekazanej do funkcji członka przez odniesienie itp.?
c++
forward-declaration
c++-faq
Igor Oks
źródło
źródło
Odpowiedzi:
Postaw się w pozycji kompilatora: kiedy przekazujesz deklarację typu, kompilator wie tylko, że ten typ istnieje; nie wie nic o swojej wielkości, członkach ani metodach. Dlatego nazywa się to niekompletnym typem . Dlatego nie można użyć tego typu do zadeklarowania elementu członkowskiego lub klasy podstawowej, ponieważ kompilator musiałby znać układ typu.
Zakładając następującą deklarację terminową.
Oto, co możesz, a czego nie możesz zrobić.
Co możesz zrobić z niepełnym typem:
Zadeklaruj członka jako wskaźnik lub odniesienie do niekompletnego typu:
Zadeklaruj funkcje lub metody, które akceptują / zwracają niekompletne typy:
Zdefiniuj funkcje lub metody, które akceptują / zwracają wskaźniki / odwołania do niekompletnego typu (ale bez użycia jego elementów):
Czego nie możesz zrobić z niekompletnym typem:
Użyj go jako klasy podstawowej
Użyj go, aby zadeklarować członka:
Zdefiniuj funkcje lub metody za pomocą tego typu
Skorzystaj z jego metod lub pól, próbując wyłuskać zmienną o niepełnym typie
Jeśli chodzi o szablony, nie ma bezwzględnej reguły: to, czy można użyć niepełnego typu jako parametru szablonu, zależy od sposobu, w jaki typ jest używany w szablonie.
Na przykład
std::vector<T>
wymaga , aby jego parametr był kompletnym typem, podczas gdyboost::container::vector<T>
nie. Czasami pełny typ jest wymagany tylko w przypadku korzystania z niektórych funkcji składowych; tak jest na przykład w przypadkustd::unique_ptr<T>
.Dobrze udokumentowany szablon powinien wskazywać w swojej dokumentacji wszystkie wymagania dotyczące jego parametrów, w tym czy muszą być kompletnymi typami, czy nie.
źródło
Główną zasadą jest to, że można deklarować tylko klasy, których układ pamięci (a zatem funkcje składowe i elementy danych) nie muszą być znane w pliku, który deklarujesz dalej.
Wykluczałoby to klasy podstawowe i wszystko inne niż klasy używane przez odwołania i wskaźniki.
źródło
Lakos rozróżnia użycie klasy
Nigdy nie widziałem tego bardziej zwięźle :)
źródło
Oprócz wskaźników i odniesień do niekompletnych typów można także deklarować prototypy funkcji, które określają parametry i / lub zwracają wartości, które są niekompletne. Nie można jednak zdefiniować funkcji mającej niekompletny parametr lub typ zwracany, chyba że jest to wskaźnik lub odwołanie.
Przykłady:
źródło
Żadna z dotychczasowych odpowiedzi nie opisuje, kiedy można użyć deklaracji forward szablonu klasy. A więc proszę bardzo.
Szablon klasy można przekazać deklarowany jako:
Po strukturze przyjętej odpowiedzi ,
Oto, co możesz, a czego nie możesz zrobić.
Co możesz zrobić z niepełnym typem:
Zadeklaruj element członkowski jako wskaźnik lub odwołanie do niekompletnego typu w innym szablonie klasy:
Zadeklaruj członka jako wskaźnik lub odniesienie do jednej z jego niekompletnych instancji:
Deklaruj szablony funkcji lub szablony funkcji członka, które akceptują / zwracają typy niekompletne:
Zadeklaruj funkcje lub funkcje składowe, które akceptują / zwracają jedną z niepełnych instancji:
Zdefiniuj szablony funkcji lub szablony funkcji członka, które akceptują / zwracają wskaźniki / odwołania do niekompletnego typu (ale bez użycia jego elementów):
Zdefiniuj funkcje lub metody, które akceptują / zwracają wskaźniki / odniesienia do jednej z jej niekompletnych instancji (ale bez użycia jej elementów):
Użyj go jako klasy podstawowej innej klasy szablonów
Użyj go, aby zadeklarować członka innego szablonu klasy:
Zdefiniuj szablony funkcji lub metody za pomocą tego typu
Czego nie możesz zrobić z niekompletnym typem:
Użyj jednej z jego instancji jako klasy podstawowej
Użyj jednej z jego instancji, aby zadeklarować członka:
Zdefiniuj funkcje lub metody za pomocą jednej z jego instancji
Użyj metod lub pól jednej z jego instancji, próbując w rzeczywistości wyłuskać zmienną o niepełnym typie
Utwórz jawne instancje szablonu klasy
źródło
X
iX<int>
są dokładnie takie same, a tylko składnia deklarująca do przodu różni się w jakikolwiek istotny sposób, przy czym wszystkie z wyjątkiem 1 linii twojej odpowiedzi to tylko wzięcie Luca is/X/X<int>/g
? Czy to naprawdę potrzebne? A może przeoczyłem drobny szczegół, który jest inny? Jest to możliwe, ale kilka razy porównałem wizualnie i nie widzę żadnych ...W pliku, w którym używasz tylko wskaźnika lub odwołania do klasy. Żadna funkcja członka / członka nie powinna być wywoływana przez ten wskaźnik / odwołanie.
z
class Foo;
// deklaracją przekazaniaMożemy zadeklarować członków danych typu Foo * lub Foo &.
Możemy deklarować (ale nie definiować) funkcje za pomocą argumentów i / lub zwracać wartości typu Foo.
Możemy zadeklarować członków danych statycznych typu Foo. Jest tak, ponieważ elementy danych statycznych są zdefiniowane poza definicją klasy.
źródło
Piszę to jako osobną odpowiedź, a nie tylko komentarz, ponieważ nie zgadzam się z odpowiedzią Luca Touraille'a, nie ze względu na legalność, ale ze względu na solidne oprogramowanie i niebezpieczeństwo błędnej interpretacji.
W szczególności mam problem z dorozumianą umową dotyczącą tego, czego oczekują użytkownicy interfejsu.
Jeśli zwracasz lub akceptujesz typy referencji, to po prostu mówisz, że mogą one przejść przez wskaźnik lub referencję, które z kolei mogą znać tylko poprzez przekazanie dalej.
Zwracając niekompletny typ,
X f2();
mówisz, że twoja osoba dzwoniąca musi mieć pełną specyfikację typu X. Potrzebują jej do stworzenia LHS lub obiektu tymczasowego w miejscu połączenia.Podobnie, jeśli zaakceptujesz niekompletny typ, program wywołujący musi zbudować obiekt, który jest parametrem. Nawet jeśli obiekt został zwrócony jako inny niekompletny typ funkcji, strona wywołująca wymaga pełnej deklaracji. to znaczy:
Myślę, że istnieje ważna zasada, że nagłówek powinien dostarczać wystarczającą ilość informacji, aby z niego korzystać, bez zależności wymagającej innych nagłówków. Oznacza to, że nagłówek powinien być w stanie zostać włączony do jednostki kompilacyjnej bez powodowania błędu kompilatora podczas korzystania z deklarowanych funkcji.
Z wyjątkiem
Jeśli pożądana jest ta zależność zewnętrzna . Zamiast korzystać z kompilacji warunkowej możesz mieć dobrze udokumentowane wymaganie, aby podawali swój własny nagłówek deklarujący X. Jest to alternatywa dla używania #ifdefs i może być użytecznym sposobem na wprowadzenie próbnych lub innych wariantów.
Ważnym rozróżnieniem są niektóre techniki szablonów, w których wyraźnie NIE oczekuje się ich tworzenia, wspomniane tylko po to, aby ktoś nie był ze mną wredny.
źródło
I disagree with Luc Touraille's answer
Napisz więc komentarz, w tym link do posta na blogu, jeśli potrzebujesz długości. To nie odpowiada na zadane pytanie. Gdyby wszyscy zastanawiali się, jak działa X, uzasadnione odpowiedzi nie zgadzają się z tym, że X robi to, lub debatują nad granicami, w których powinniśmy ograniczać naszą swobodę korzystania z X - prawie nie mielibyśmy prawdziwych odpowiedzi.Ogólna zasada, której przestrzegam, nie zawiera plików nagłówkowych, chyba że muszę. Więc jeśli nie przechowuję obiektu klasy jako zmiennej członka mojej klasy, nie dołączę go, użyję tylko deklaracji forward.
źródło
Tak długo, jak nie potrzebujesz definicji (myśl wskaźniki i referencje), możesz uniknąć deklaracji forward. Właśnie dlatego najczęściej można je zobaczyć w nagłówkach, podczas gdy pliki implementacyjne zwykle ściągają nagłówek odpowiedniej definicji.
źródło
Zazwyczaj będziesz chciał użyć deklaracji przekazywania w pliku nagłówkowym klas, gdy chcesz użyć innego typu (klasy) jako członka klasy. Nie można używać metod klas zadeklarowanych w przód w pliku nagłówkowym, ponieważ C ++ nie zna jeszcze definicji tej klasy w tym momencie. To logika, którą musisz przenieść do plików .cpp, ale jeśli używasz funkcji szablonów, powinieneś zredukować je tylko do części, która używa szablonu i przenieść tę funkcję do nagłówka.
źródło
Weźmy pod uwagę, że deklaracja przekazania spowoduje kompilację kodu (tworzony jest obiekt obj). Łączenie (tworzenie exe) nie powiedzie się, dopóki nie zostaną znalezione definicje.
źródło
class A; class B { A a; }; int main(){}
i daj mi znać, jak to działa . Oczywiście, że się nie skompiluje. Wszystkie prawidłowe odpowiedzi tutaj wyjaśniają powód i dokładne, ograniczone konteksty, w których deklaracja forward jest ważna. Zamiast tego napisałeś o czymś zupełnie innym.Chcę tylko dodać jedną ważną rzecz, którą możesz zrobić z przekazaną klasą niewymienioną w odpowiedzi Luca Touraille'a.
Co możesz zrobić z niepełnym typem:
Zdefiniuj funkcje lub metody, które akceptują / zwracają wskaźniki / referencje do niekompletnego typu i przekazują te wskaźniki / referencje do innej funkcji.
Moduł może przekazać obiekt deklarowanej do przodu klasy do innego modułu.
źródło
Jako, że Luc Touraille już bardzo dobrze wyjaśnił, gdzie używać, a nie używać terminowej deklaracji klasy.
Dodam tylko do tego, dlaczego musimy go używać.
W miarę możliwości powinniśmy używać deklaracji Forward, aby uniknąć niepożądanego wstrzykiwania zależności.
W
#include
związku z tym, że pliki nagłówkowe są dodawane do wielu plików, jeśli dodamy nagłówek do innego pliku nagłówka, doda niechciane wstrzyknięcie zależności w różnych częściach kodu źródłowego, którego można uniknąć, dodając#include
nagłówek do.cpp
plików tam, gdzie to możliwe, zamiast dodawać do innego pliku nagłówka i w miarę możliwości używaj deklaracji przekazywania klasy w.h
plikach nagłówkowych .źródło