Ich versuche so etwas zu tun:
enum E;
void Foo(E e);
enum E {A, B, C};
was der Compiler ablehnt. Ich habe einen kurzen Blick auf Google geworfen und der Konsens scheint zu lauten: "Sie können es nicht tun", aber ich kann nicht verstehen, warum. Kann jemand erklären?
Erläuterung 2: Ich mache dies, da ich private Methoden in einer Klasse habe, die diese Aufzählung verwenden, und ich möchte nicht, dass die Werte der Aufzählung verfügbar gemacht werden. Daher möchte ich beispielsweise nicht, dass jemand weiß, dass E definiert ist als
enum E {
FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}
Als Projekt X möchte ich nicht, dass meine Benutzer davon erfahren.
Daher wollte ich die Aufzählung weiter deklarieren, damit ich die privaten Methoden in die Header-Datei einfügen, die Aufzählung intern in der cpp deklarieren und die erstellte Bibliotheksdatei und den Header an andere Personen verteilen kann.
Der Compiler ist GCC.
Antworten:
Der Grund, warum die Aufzählung nicht vorwärts deklariert werden kann, ist, dass der Compiler ohne Kenntnis der Werte den für die Aufzählungsvariable erforderlichen Speicher nicht kennen kann. C ++ - Compiler dürfen den tatsächlichen Speicherplatz basierend auf der Größe angeben, die erforderlich ist, um alle angegebenen Werte zu enthalten. Wenn nur die Vorwärtsdeklaration sichtbar ist, kann die Übersetzungseinheit nicht wissen, welche Speichergröße ausgewählt wurde - es kann sich um ein Zeichen oder ein Int oder etwas anderes handeln.
Aus Abschnitt 7.2.5 des ISO C ++ - Standards:
Da der Aufrufer der Funktion die Größe der Parameter kennen muss, um den Aufrufstapel korrekt einzurichten, muss die Anzahl der Aufzählungen in einer Aufzählungsliste vor dem Funktionsprototyp bekannt sein.
Update: In C ++ 0X wurde eine Syntax für die Vorwärtsdeklaration von Aufzählungstypen vorgeschlagen und akzeptiert. Sie können den Vorschlag unter http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf sehen
quelle
struct S; void foo(S s);
(beachten Sie, dass diesfoo
nur deklariert und nicht definiert ist), gibt es keinen Grund, warum wir dies nicht auch tun könntenenum E; void foo(E e);
. In beiden Fällen wird die Größe nicht benötigt.Die Vorwärtsdeklaration von Aufzählungen ist seit C ++ 11 möglich. Bisher konnten Aufzählungstypen nicht vorwärts deklariert werden, da die Größe der Aufzählung von ihrem Inhalt abhängt. Solange die Größe der Aufzählung von der Anwendung angegeben wird, kann sie vorwärts deklariert werden:
quelle
Angesichts der jüngsten Entwicklungen füge ich hier eine aktuelle Antwort hinzu.
Sie können eine Aufzählung in C ++ 11 vorwärts deklarieren, solange Sie gleichzeitig ihren Speichertyp deklarieren. Die Syntax sieht folgendermaßen aus:
Wenn sich die Funktion nie auf die Werte der Aufzählung bezieht, benötigen Sie zu diesem Zeitpunkt überhaupt keine vollständige Deklaration.
Dies wird von G ++ 4.6 und höher (
-std=c++0x
oder-std=c++11
in neueren Versionen) unterstützt. Visual C ++ 2013 unterstützt dies. In früheren Versionen gibt es eine nicht standardmäßige Unterstützung, die ich noch nicht herausgefunden habe. Ich habe einen Vorschlag gefunden, dass eine einfache Vorwärtserklärung legal ist, aber YMMV.quelle
enum class
als C ++ - Erweiterung (bevor C ++ 11 anders warenum class
) zurückportiert , zumindest wenn ich mich richtig erinnere. Der Compiler ermöglichte es Ihnen, den zugrunde liegenden Typ einer Aufzählung anzugeben, unterstützte jedoch keineenum class
Aufzählungen oder gab sie nicht weiter und warnte Sie, dass die Qualifizierung eines Enumerators mit dem Umfang der Aufzählung eine nicht standardmäßige Erweiterung darstellt. Ich erinnere mich, dass es ungefähr genauso funktioniert wie die Angabe des zugrunde liegenden Typs in C ++ 11, außer dass es ärgerlicher ist, weil Sie die Warnung unterdrücken mussten.Das Vorwärtsdeklarieren von Dingen in C ++ ist sehr nützlich, da es die Kompilierungszeit erheblich verkürzt . Sie können vorwärts mehrere Dinge in C ++ deklarieren einschließlich:
struct
,class
,function
, etc ...Aber können Sie eine
enum
in C ++ deklarieren ?Nein, kannst du nicht.
Aber warum nicht zulassen? Wenn es erlaubt wäre, könnten Sie Ihren
enum
Typ in Ihrer Header-Datei und Ihreenum
Werte in Ihrer Quelldatei definieren. Klingt so, als ob es erlaubt sein sollte, oder?Falsch.
In C ++ gibt es keinen Standardtyp für
enum
C # (int). In C ++ wird Ihrenum
Typ vom Compiler als ein beliebiger Typ festgelegt, der dem Wertebereich entspricht, den Sie für Ihren habenenum
.Was bedeutet das?
Dies bedeutet, dass der
enum
zugrunde liegende Typ Ihres Unternehmens erst dann vollständig bestimmt werden kann, wenn Sie alle Werte desenum
definierten Typs haben . Welchen Mann können Sie die Erklärung und Definition Ihrer nicht trennenenum
. Und deshalb können Sie eineenum
in C ++ nicht weiterleiten .Der ISO C ++ Standard S7.2.5:
Sie können die Größe eines Aufzählungstyps in C ++ mithilfe des
sizeof
Operators bestimmen . Die Größe des Aufzählungstyps entspricht der Größe des zugrunde liegenden Typs. Auf diese Weise können Sie erraten, welchen Typ Ihr Compiler für Sie verwendetenum
.Was ist, wenn Sie den Typ Ihres
enum
explizit so angeben :Können Sie dann Ihre weiterleiten
enum
?Aber warum nicht?
Die Angabe des Typs von
enum
ist nicht Teil des aktuellen C ++ - Standards. Es ist eine VC ++ - Erweiterung. Es wird jedoch Teil von C ++ 0x sein.Quelle
quelle
[Meine Antwort ist falsch, aber ich habe sie hier gelassen, weil die Kommentare nützlich sind].
Das Vorwärtsdeklarieren von Aufzählungen ist nicht Standard, da nicht garantiert wird, dass Zeiger auf verschiedene Aufzählungstypen dieselbe Größe haben. Der Compiler muss möglicherweise die Definition anzeigen, um zu wissen, welche Größenzeiger mit diesem Typ verwendet werden können.
In der Praxis haben Zeiger auf Aufzählungen zumindest bei allen gängigen Compilern eine einheitliche Größe. Die Vorwärtsdeklaration von Aufzählungen wird beispielsweise von Visual C ++ als Spracherweiterung bereitgestellt.
quelle
Es gibt in der Tat keine Vorwärtserklärung der Aufzählung. Da die Definition einer Aufzählung keinen Code enthält, der von anderem Code abhängen könnte, der die Aufzählung verwendet, ist es normalerweise kein Problem, die Aufzählung vollständig zu definieren, wenn Sie sie zum ersten Mal deklarieren.
Wenn Ihre Aufzählung nur von Funktionen privater Mitglieder verwendet wird, können Sie die Kapselung implementieren, indem Sie die Aufzählung selbst als privates Mitglied dieser Klasse verwenden. Die Aufzählung muss zum Zeitpunkt der Deklaration, dh innerhalb der Klassendefinition, noch vollständig definiert sein. Dies ist jedoch kein größeres Problem, da dort private Mitgliederfunktionen deklariert werden, und es ist keine schlechtere Darstellung von Implementierungsinternalen als diese.
Wenn Sie einen tieferen Grad an Verschleierung für Ihre Implementierungsdetails benötigen, können Sie diese in eine abstrakte Schnittstelle aufteilen, die nur aus reinen virtuellen Funktionen und einer konkreten, vollständig verborgenen Klasse besteht, die die Schnittstelle implementiert (erbt). Das Erstellen von Klasseninstanzen kann von einer Factory- oder einer statischen Elementfunktion der Schnittstelle durchgeführt werden. Auf diese Weise wird auch der tatsächliche Klassenname, geschweige denn seine privaten Funktionen, nicht angezeigt.
quelle
Ich stelle nur fest, dass der Grund tatsächlich ist dass die Größe der Aufzählung nach der Vorwärtsdeklaration noch nicht bekannt ist. Nun, Sie verwenden die Vorwärtsdeklaration einer Struktur, um einen Zeiger herumgeben oder auf ein Objekt von einer Stelle verweisen zu können, auf die auch in der vorwärts deklarierten Strukturdefinition selbst verwiesen wird.
Das Vorwärtsdeklarieren einer Aufzählung wäre nicht allzu nützlich, da man den Aufzählungs-By-Wert weitergeben möchte. Sie konnten nicht einmal einen Zeiger darauf haben, weil mir kürzlich mitgeteilt wurde, dass einige Plattformen Zeiger unterschiedlicher Größe für char als für int oder long verwenden. Es kommt also alles auf den Inhalt der Aufzählung an.
Der aktuelle C ++ Standard verbietet ausdrücklich, so etwas zu tun
(in
7.1.5.3/1
). Der nächste C ++ - Standard für das nächste Jahr erlaubt jedoch Folgendes, was mich überzeugt hat, dass das Problem tatsächlich mit dem zugrunde liegenden Typ zu tun hat :Es ist als "undurchsichtige" Enumeration bekannt. Sie können im folgenden Code sogar X nach Wert verwenden. Und seine Enumeratoren können später in einer späteren Neudeklaration der Enumeration definiert werden. Siehe
7.2
im aktuellen Arbeitsentwurf.quelle
Ich würde es so machen:
[im öffentlichen Header]
[im internen Header]
Durch Hinzufügen von FORCE_32BIT stellen wir sicher, dass Econtent zu einem langen kompiliert wird, sodass es mit E austauschbar ist.
quelle
Wenn Sie wirklich nicht möchten, dass Ihre Aufzählung in Ihrer Header-Datei angezeigt wird UND sicherstellen, dass sie nur von privaten Methoden verwendet wird, kann eine Lösung darin bestehen, das Pimpl-Prinzip zu verwenden.
Es ist eine Technik, die sicherstellt, dass die Klasseneinbauten in den Kopfzeilen ausgeblendet werden, indem nur Folgendes deklariert wird:
Anschließend deklarieren Sie in Ihrer Implementierungsdatei (cpp) eine Klasse, die die Interna darstellt.
Sie müssen die Implementierung dynamisch im Klassenkonstruktor erstellen und im Destruktor löschen. Bei der Implementierung der öffentlichen Methode müssen Sie Folgendes verwenden:
Es gibt Vorteile für die Verwendung von pimpl. Zum einen entkoppelt es Ihren Klassenheader von seiner Implementierung, ohne dass andere Klassen neu kompiliert werden müssen, wenn eine Klassenimplementierung geändert wird. Ein weiterer Grund ist, dass Sie Ihre Kompilierungszeit beschleunigen, weil Ihre Header so einfach sind.
Aber es ist ein Schmerz zu benutzen, also sollten Sie sich wirklich fragen, ob es so schwierig ist, Ihre Aufzählung als privat in der Kopfzeile zu deklarieren.
quelle
Sie können die Aufzählung in eine Struktur einschließen, einige Konstruktoren und Typkonvertierungen hinzufügen und stattdessen die Struktur weiter deklarieren.
Dies scheint zu funktionieren: http://ideone.com/TYtP2
quelle
Scheint, dass es in GCC nicht vorwärts deklariert werden kann!
Interessante Diskussion hier
quelle
Es gibt einige Meinungsverschiedenheiten, da dies (irgendwie) gestoßen wurde, also hier einige relevante Teile aus dem Standard. Untersuchungen zeigen, dass der Standard weder eine Vorwärtsdeklaration wirklich definiert noch explizit angibt, dass Aufzählungen vorwärtsdeklariert werden können oder nicht.
Zunächst aus dcl.enum, Abschnitt 7.2:
Der zugrunde liegende Typ einer Aufzählung ist also implementierungsdefiniert, mit einer geringfügigen Einschränkung.
Als nächstes blättern wir zu dem Abschnitt über "unvollständige Typen" (3.9), der ungefähr so nahe kommt, wie wir es mit einem Standard für Vorwärtsdeklarationen erreichen:
Dort hat der Standard also ziemlich genau die Typen festgelegt, die vorwärts deklariert werden können. Enum war nicht vorhanden, daher betrachten Compilerautoren die Vorwärtserklärung aufgrund der variablen Größe des zugrunde liegenden Typs im Allgemeinen als vom Standard nicht zulässig.
Es macht auch Sinn. Aufzählungen werden normalerweise in Situationen nach Wert referenziert, und der Compiler müsste tatsächlich die Speichergröße in diesen Situationen kennen. Da die Speichergröße durch die Implementierung definiert ist, verwenden viele Compiler möglicherweise nur 32-Bit-Werte für den zugrunde liegenden Typ jeder Aufzählung. Ab diesem Zeitpunkt können sie deklariert werden. Ein interessantes Experiment könnte darin bestehen, eine Enumeration in Visual Studio vorwärts zu deklarieren und sie dann zu zwingen, einen zugrunde liegenden Typ zu verwenden, der größer als sizeof (int) ist, wie oben erläutert, um zu sehen, was passiert.
quelle
Für VC ist hier der Test zur Vorwärtsdeklaration und Angabe des zugrunde liegenden Typs:
Habe aber die Warnung für / W4 erhalten (/ W3 hat diese Warnung nicht erhalten)
Warnung C4480: Nicht standardmäßige Erweiterung verwendet: Angabe des zugrunde liegenden Typs für die Aufzählung 'T'
VC (Microsoft (R) 32-Bit-C / C ++ - Optimierungscompiler Version 15.00.30729.01 für 80x86) sieht im obigen Fall fehlerhaft aus:
Der obige Assembler-Code wird direkt aus /Fatest.asm extrahiert, nicht meine persönliche Vermutung. Sehen Sie den mov DWORD PTR [eax], 305419896; 12345678H Linie?
Das folgende Code-Snippet beweist es:
Das Ergebnis ist: 0x78, 0x56, 0x34, 0x12
Die obige Schlüsselanweisung lautet:
mov BYTE PTR [eax], 120; 00000078H
Das Endergebnis ist: 0x78, 0x1, 0x1, 0x1
Beachten Sie, dass der Wert nicht überschrieben wird
Die Verwendung der Forward-Deklaration von enum in VC wird daher als schädlich angesehen.
Übrigens ist es nicht überraschend, dass die Syntax für die Deklaration des zugrunde liegenden Typs dieselbe ist wie in C #. In der Praxis habe ich festgestellt, dass es sich lohnt, 3 Bytes zu sparen, indem der zugrunde liegende Typ als char angegeben wird, wenn mit dem eingebetteten System gesprochen wird, das nur über begrenzten Speicher verfügt.
quelle
In meinen Projekten habe ich die Namespace-Bound-Enumeration- Technik angewendet, um mit
enum
s aus Legacy- und Drittanbieter-Komponenten umzugehen . Hier ist ein Beispiel:forward.h:
enum.h:
foo.h:
foo.cc:
main.cc:
Beachten Sie, dass der
foo.h
Header nichts wissen musslegacy::evil
. Nur die Dateien, die den Legacy-Typlegacy::evil
(hier: main.cc) verwenden, müssen enthalten seinenum.h
.quelle
Meine Lösung für Ihr Problem wäre entweder:
1 - Verwenden Sie int anstelle von Aufzählungen: Deklarieren Sie Ihre Ints in einem anonymen Namespace in Ihrer CPP-Datei (nicht im Header):
Da Ihre Methoden privat sind, wird niemand mit den Daten herumspielen. Sie können sogar noch weiter gehen, um zu testen, ob Ihnen jemand ungültige Daten sendet:
2: Erstellen Sie eine vollständige Klasse mit begrenzten Konstanteninstanziierungen, wie in Java. Forward deklariert die Klasse, definiert sie dann in der CPP-Datei und instanziiert nur die enumartigen Werte. Ich habe so etwas in C ++ gemacht, und das Ergebnis war nicht so zufriedenstellend wie gewünscht, da es Code brauchte, um eine Aufzählung zu simulieren (Kopierkonstruktion, Operator = usw.).
3: Verwenden Sie wie zuvor vorgeschlagen die privat deklarierte Aufzählung. Trotz der Tatsache, dass ein Benutzer seine vollständige Definition sieht, kann er sie weder verwenden noch die privaten Methoden verwenden. Daher können Sie normalerweise die Aufzählung und den Inhalt der vorhandenen Methoden ändern, ohne den Code mithilfe Ihrer Klasse neu kompilieren zu müssen.
Meine Vermutung wäre entweder die Lösung 3 oder 1.
quelle
Da die Aufzählung eine ganzzahlige Integralgröße haben kann (der Compiler entscheidet, welche Größe eine bestimmte Aufzählung hat), kann der Zeiger auf die Aufzählung auch eine unterschiedliche Größe haben, da es sich um einen ganzzahligen Typ handelt (Zeichen haben auf einigen Plattformen Zeiger unterschiedlicher Größe zum Beispiel).
Der Compiler kann also nicht einmal zulassen, dass Sie die Aufzählung vorwärts deklarieren und dem Benutzer einen Zeiger darauf geben, da er selbst dort die Größe der Aufzählung benötigt.
quelle
Sie definieren eine Aufzählung, um die möglichen Werte von Elementen des Typs auf eine begrenzte Menge zu beschränken. Diese Einschränkung ist zur Kompilierungszeit durchzusetzen.
Wenn Sie vorwärts deklarieren, dass Sie später einen "begrenzten Satz" verwenden, wird kein Wert hinzugefügt: Der nachfolgende Code muss die möglichen Werte kennen, um davon profitieren zu können.
Obwohl der Compiler über die Größe des Aufzählungstyps besorgt ist, geht die Absicht der Aufzählung verloren, wenn Sie sie weiterleiten.
quelle