Próbuję zrobić coś takiego:
enum E;
void Foo(E e);
enum E {A, B, C};
które kompilator odrzuca. Rzuciłem okiem na Google i wydaje się, że konsensus brzmi: „nie możesz tego zrobić”, ale nie rozumiem dlaczego. Czy ktoś może wyjaśnić?
Wyjaśnienie 2: Robię to, ponieważ mam prywatne metody w klasie, która przyjmuje wymieniony wyliczenie i nie chcę ujawniać wartości wyliczenia - więc na przykład nie chcę, aby ktokolwiek wiedział, że E jest zdefiniowane jako
enum E {
FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}
ponieważ projekt X nie jest czymś, o czym chcę wiedzieć.
Tak więc chciałem przekazać deklarację wyliczenia, aby móc umieścić metody prywatne w pliku nagłówka, zadeklarować wyliczenie wewnętrznie w procesorze i rozpowszechniać zbudowany plik biblioteki i nagłówek wśród ludzi.
Co do kompilatora - to GCC.
Odpowiedzi:
Powodem, dla którego wyliczenie nie może być zadeklarowane, jest to, że bez znajomości wartości kompilator nie może znać wymaganej pamięci dla zmiennej wyliczeniowej. Kompilatory C ++ mogą określać rzeczywistą przestrzeń dyskową na podstawie rozmiaru niezbędnego do przechowywania wszystkich podanych wartości. Jeśli wszystko, co jest widoczne, to deklaracja przesyłania dalej, jednostka tłumacząca nie może wiedzieć, jaki rozmiar pamięci zostanie wybrany - może to być znak, int lub coś innego.
Z sekcji 7.2.5 normy ISO C ++:
Ponieważ wywołujący funkcję musi znać rozmiary parametrów, aby poprawnie skonfigurować stos wywołań, liczba wyliczeń na liście wyliczeń musi być znana przed prototypem funkcji.
Aktualizacja: W C ++ 0X zaproponowano i przyjęto składnię deklaracji typów wyliczeniowych do przodu. Propozycję można zobaczyć na stronie http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf
źródło
struct S; void foo(S s);
(zwróć uwagę, żefoo
jest to tylko zadeklarowane, nieokreślone), nie ma powodu, dla którego nie moglibyśmy zrobićenum E; void foo(E e);
tak dobrze. W obu przypadkach rozmiar nie jest potrzebny.Przekazanie deklaracji wyliczeń jest możliwe od wersji C ++ 11. Poprzednio typy wyliczeń nie mogły być deklarowane dalej, ponieważ rozmiar wyliczenia zależy od jego zawartości. Dopóki rozmiar wyliczenia jest określony przez aplikację, można go zadeklarować w przód:
źródło
Dodam tutaj aktualną odpowiedź, biorąc pod uwagę ostatnie zmiany.
Możesz zadeklarować enum w C ++ 11, pod warunkiem, że zadeklarujesz jego typ przechowywania w tym samym czasie. Składnia wygląda następująco:
W rzeczywistości, jeśli funkcja nigdy nie odwołuje się do wartości wyliczenia, w tym momencie nie potrzebujesz kompletnej deklaracji.
Jest to obsługiwane przez G ++ 4.6 i nowsze (
-std=c++0x
lub-std=c++11
w nowszych wersjach). Visual C ++ 2013 obsługuje to; we wcześniejszych wersjach ma jakieś niestandardowe wsparcie, którego jeszcze nie rozgryzłem - znalazłem sugestię, że prosta deklaracja przekazania jest legalna, ale YMMV.źródło
enum class
jako rozszerzenie C ++ (przed innymi C ++ 11enum class
), przynajmniej jeśli dobrze pamiętam. Kompilator pozwolił ci określić typ bazowy wyliczenia, ale nie obsługiwałenum class
ani nie zadeklarował dalej wyliczeń, i ostrzegł cię, że kwalifikowanie modułu wyliczającego zakresem wyliczenia jest niestandardowym rozszerzeniem. Pamiętam, że działał mniej więcej tak samo jak w przypadku określania podstawowego typu w C ++ 11, z wyjątkiem bardziej irytujących, ponieważ musiałeś stłumić ostrzeżenie.Przekazywanie rzeczy do przodu w C ++ jest bardzo przydatne, ponieważ znacznie przyspiesza czas kompilacji . Można do przodu zadeklarować kilka rzeczy w C ++, w tym:
struct
,class
,function
, itd ...Ale czy możesz przekazać deklarację
enum
w C ++?Nie, nie możesz.
Ale dlaczego nie pozwolić na to? Jeśli było to dozwolone, możesz zdefiniować swój
enum
typ w pliku nagłówkowym, aenum
wartości w pliku źródłowym. Wygląda na to, że powinno być dozwolone, prawda?Źle.
W C ++ nie ma domyślnego typu,
enum
podobnie jak w C # (int). W C ++ Twójenum
typ zostanie określony przez kompilator jako dowolny typ, który będzie pasował do zakresu twoich wartościenum
.Co to znaczy?
Oznacza to, że
enum
typ podstawowy nie może być w pełni określony, dopóki nie uzyska się wszystkich wartościenum
zdefiniowanych. Których nie możesz rozdzielić deklaracji i definicji swojegoenum
. I dlatego nie można przekazać deklaracjienum
w C ++.Norma ISO C ++ S7.2.5:
Możesz określić rozmiar wyliczonego typu w C ++ za pomocą
sizeof
operatora. Rozmiar wyliczonego typu jest rozmiarem jego podstawowego typu. W ten sposób możesz odgadnąć, jakiego typu używa Twój kompilatorenum
.Co się stanie, jeśli
enum
wyraźnie określisz typ swojego :Czy możesz następnie zgłosić swój
enum
?Nie. Ale dlaczego nie?
Określenie typu an
enum
nie jest faktycznie częścią bieżącego standardu C ++. Jest to rozszerzenie VC ++. Będzie to jednak część C ++ 0x.Źródło
źródło
[Moja odpowiedź jest błędna, ale zostawiłem ją tutaj, ponieważ komentarze są przydatne].
Przekazywanie wyliczeń do przodu jest niestandardowe, ponieważ nie można zagwarantować, że wskaźniki do różnych typów wyliczeń mają ten sam rozmiar. Kompilator może potrzebować zobaczyć definicję, aby wiedzieć, jakie wskaźniki wielkości mogą być używane z tym typem.
W praktyce, przynajmniej we wszystkich popularnych kompilatorach, wskaźniki do wyliczeń mają stały rozmiar. Forward deklaracja wyliczeń jest dostarczana jako rozszerzenie języka na przykład przez Visual C ++.
źródło
Rzeczywiście nie ma czegoś takiego jak terminowa deklaracja wyliczenia. Ponieważ definicja wyliczenia nie zawiera żadnego kodu, który mógłby zależeć od innego kodu używającego wyliczenia, zazwyczaj nie ma problemu z całkowitym zdefiniowaniem wyliczenia przy pierwszym deklarowaniu.
Jeśli jedynym użytkiem twojego wyliczenia są prywatne funkcje składowe, możesz zaimplementować enkapsulację, mając wyliczenie jako prywatny członek tej klasy. Wyliczenie wciąż musi być w pełni zdefiniowane w punkcie deklaracji, czyli w ramach definicji klasy. Nie jest to jednak większy problem, ponieważ zadeklarowanie w nim funkcji prywatnego członka i nie jest gorszym ujawnieniem wewnętrznych elementów implementacyjnych niż to.
Jeśli potrzebujesz głębszego ukrywania szczegółów implementacji, możesz rozbić go na abstrakcyjny interfejs, składający się wyłącznie z czysto wirtualnych funkcji i konkretnego, całkowicie ukrytego interfejsu implementującego (dziedziczącego) klasę. Tworzenie instancji klas może być obsługiwane przez fabryczną lub statyczną funkcję elementu interfejsu. W ten sposób nawet prawdziwa nazwa klasy, nie mówiąc już o jej prywatnych funkcjach, nie zostanie ujawniona.
źródło
Wystarczy zauważyć, że tak naprawdę powodem jest to, że rozmiar wyliczenia nie jest jeszcze znany po deklaracji forward. Cóż, używasz deklaracji forward struktury, aby móc przesuwać wskaźnik dookoła lub odwoływać się do obiektu z miejsca, do którego odwołuje się sama definicja deklarowanej struktury.
Przekazywanie wyliczenia do przodu nie byłoby zbyt przydatne, ponieważ ktoś chciałby móc omijać wartość wyliczeniową. Nie mogłeś nawet mieć do niego wskaźnika, ponieważ niedawno powiedziano mi, że niektóre platformy używają wskaźników różnej wielkości dla char niż dla int lub long. Wszystko zależy więc od zawartości wyliczenia.
Obecny standard C ++ wyraźnie zabrania robienia czegoś podobnego
(w
7.1.5.3/1
). Ale następnego C ++ standard ze względu na następny rok umożliwia następujące dane, które przekonały mnie problem faktycznie ma do czynienia z typem bazowym:Jest znany jako „nieprzezroczysta” deklaracja wyliczeniowa. Możesz nawet użyć X według wartości w następującym kodzie. A jego wyliczacze można później zdefiniować w późniejszej deklaracji wyliczenia. Zobacz
7.2
w bieżącym roboczym projekcie.źródło
Zrobiłbym to w ten sposób:
[w nagłówku publicznym]
[w nagłówku wewnętrznym]
Dodając FORCE_32BIT, zapewniamy, że Econtent kompiluje się do długiej, więc jest wymienna z E.
źródło
Jeśli naprawdę nie chcesz, aby twój enum pojawiał się w pliku nagłówkowym ORAZ upewnij się, że jest używany tylko przez prywatne metody, wówczas jednym z rozwiązań może być zastosowanie zasady pimpl.
Jest to technika, która zapewnia ukrywanie wewnętrznych elementów klasy w nagłówkach, po prostu deklarując:
Następnie w pliku implementacji (cpp) deklarujesz klasę, która będzie reprezentacją elementów wewnętrznych.
Należy dynamicznie utworzyć implementację w konstruktorze klas i usunąć ją w destruktorze, a podczas implementowania metody publicznej należy użyć:
Korzystanie z pimpl ma swoje zalety, jednym z nich jest to, że oddziela nagłówek klasy od jego implementacji, nie ma potrzeby ponownej kompilacji innych klas przy zmianie implementacji jednej klasy. Innym jest przyspieszenie czasu kompilacji, ponieważ nagłówki są tak proste.
Ale korzystanie z niego jest uciążliwe, dlatego naprawdę powinieneś zadać sobie pytanie, czy po prostu zadeklarowanie wyliczenia jako prywatnego w nagłówku jest tak dużym problemem.
źródło
Możesz owinąć wyliczenie w strukturze, dodając niektóre konstruktory i konwersje typów, i zamiast tego zadeklaruj strukturę w przód.
Wygląda na to, że działa: http://ideone.com/TYtP2
źródło
Wydaje się, że nie można go zadeklarować w GCC!
Interesująca dyskusja tutaj
źródło
Jest trochę sprzeciwu, odkąd to się podbiło (w pewnym sensie), więc oto kilka istotnych bitów ze standardu. Badania pokazują, że norma tak naprawdę nie definiuje deklaracji forward, ani nie stwierdza wprost, że wyliczenia mogą lub nie mogą być deklarowane forward.
Po pierwsze, z dcl.enum, sekcja 7.2:
Zatem podstawowy typ wyliczenia jest definiowany implementacyjnie, z jednym niewielkim ograniczeniem.
Następnie przechodzimy do sekcji „typy niekompletne” (3.9), która jest prawie tak bliska, jak zbliżamy się do dowolnego standardu w deklaracjach forward:
Tak więc, standard w zasadzie określa typy, które mogą być zadeklarowane w przód. Enum nie było tam, więc autorzy kompilatora ogólnie uważają deklarację forward za niedozwoloną przez standard ze względu na zmienną wielkość jego podstawowego typu.
To też ma sens. Do wyliczeń zwykle odwołuje się w sytuacjach według wartości, a kompilator rzeczywiście musiałby znać wielkość pamięci w takich sytuacjach. Ponieważ rozmiar pamięci jest zdefiniowany w implementacji, wiele kompilatorów może po prostu wybrać 32-bitowe wartości dla podstawowego typu każdego wyliczenia, w którym to momencie możliwe staje się przekazanie dalej deklaracji. Ciekawym eksperymentem może być próba zadeklarowania wyliczenia w studio wizualnym, a następnie zmuszenie go do użycia typu podstawowego większego niż sizeof (int), jak wyjaśniono powyżej, aby zobaczyć, co się stanie.
źródło
W przypadku VC oto test dotyczący deklaracji forward i określenia typu bazowego:
Ale dostałem ostrzeżenie dla / W4 (/ W3 nie wyświetla tego ostrzeżenia)
ostrzeżenie C4480: używane niestandardowe rozszerzenie: określenie typu bazowego dla wyliczenia „T”
VC (Microsoft (R) 32-bitowy kompilator optymalizujący C / C ++ w wersji 15.00.30729.01 dla 80x86) wygląda wadliwie w powyższym przypadku:
Powyższy kod zestawu jest pobierany bezpośrednio z pliku /Fatest.asm, a nie w moich osobistych przypuszczeniach. Czy widzisz mov DWORD PTR [eax], 305419896; Linia 12345678H?
udowadnia to następujący fragment kodu:
wynikiem jest: 0x78, 0x56, 0x34, 0x12
powyższa kluczowa instrukcja staje się:
mov BYTE PTR [eax], 120; 00000078H
końcowy wynik to: 0x78, 0x1, 0x1, 0x1
Uwaga: wartość nie jest nadpisywana
Tak więc zastosowanie deklaracji wyliczeniowej wyliczenia w VC jest uważane za szkodliwe.
BTW, żeby się nie dziwić, składnia deklaracji typu bazowego jest taka sama jak w C #. W praktyce stwierdziłem, że warto oszczędzić 3 bajty, określając typ podstawowy jako char podczas rozmowy z systemem osadzonym, który ma ograniczoną pamięć.
źródło
W swoich projektach zastosowałem technikę wyliczania granic nazw , aby radzić sobie ze
enum
s ze starszych elementów i komponentów innych firm. Oto przykład:do przodu. h:
enum.h:
foo.h:
foo.cc:
main.cc:
Pamiętaj, że
foo.h
nagłówek nie musi nic wiedziećlegacy::evil
.legacy::evil
Uwzględnić należy tylko pliki, które używają starszego typu (tutaj: main.cc)enum.h
.źródło
Moim rozwiązaniem Twojego problemu byłoby:
1 - użyj int zamiast enums: Zadeklaruj int w anonimowej przestrzeni nazw w pliku CPP (nie w nagłówku):
Ponieważ twoje metody są prywatne, nikt nie będzie bałagan z danymi. Możesz nawet przejść dalej, aby sprawdzić, czy ktoś wyśle Ci nieprawidłowe dane:
2: utwórz pełną klasę z ograniczonymi instancjami stałych, jak w Javie. Przekaż zadeklaruj klasę, a następnie zdefiniuj ją w pliku CPP i zaimplementuj tylko wartości podobne do wyliczania. Zrobiłem coś takiego w C ++, a wynik nie był tak satysfakcjonujący, jak to pożądane, ponieważ potrzebował trochę kodu do symulacji wyliczenia (konstrukcja kopii, operator = itp.).
3: Jak wcześniej zaproponowano, użyj enum deklarowanego prywatnie. Pomimo tego, że użytkownik zobaczy pełną definicję, nie będzie w stanie jej używać ani używać metod prywatnych. Zwykle będziesz więc mógł modyfikować wyliczenie i zawartość istniejących metod bez potrzeby ponownej kompilacji kodu przy użyciu swojej klasy.
Domyślam się, że będzie to rozwiązanie 3 lub 1.
źródło
Ponieważ wyliczenie może być całkowitym rozmiarem o różnej wielkości (kompilator decyduje, jaki rozmiar ma dany wyliczenie), wskaźnik do wyliczenia może mieć również różny rozmiar, ponieważ jest to typ integralny (znaki mają wskaźniki o innym rozmiarze na niektórych platformach na przykład).
Kompilator nie może więc nawet zadeklarować do przodu wyliczenia i użyć do niego wskaźnika, ponieważ nawet tam potrzebuje rozmiaru wyliczenia.
źródło
Zdefiniuj wyliczenie, aby ograniczyć możliwe wartości elementów tego typu do ograniczonego zestawu. To ograniczenie należy egzekwować w czasie kompilacji.
Podczas przekazywania dalej deklaracji, że później użyjesz „ograniczonego zestawu”, nie dodaje żadnej wartości: kolejny kod musi znać możliwe wartości, aby z niego skorzystać.
Chociaż kompilator jest zaniepokojony rozmiarem typu wyliczanego, zamiar wyliczenia gubi się, gdy przekażesz go dalej.
źródło