Weiterleiten einer Aufzählung in C ++


263

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.


So viele Jahre später hat mich StackOverflow irgendwie zurückgelockt;) Als postmortalen Vorschlag - tun Sie dies nur nicht besonders in dem von Ihnen beschriebenen Szenario. Ich würde es vorziehen, eine abstrakte Schnittstelle zu definieren und diese den Benutzern zur Verfügung zu stellen und die Aufzählungsdefinition und alle anderen Implementierungsdetails mit der internen Implementierung beizubehalten, die niemand sonst auf meiner Seite sieht, sodass ich jederzeit alles tun kann und die volle Kontrolle darüber habe, wann Benutzer sehen etwas.
RnR

Antworten:


216

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:

Der zugrunde liegende Typ einer Aufzählung ist ein integraler Typ, der alle in der Aufzählung definierten Aufzählungswerte darstellen kann. Es ist implementierungsdefiniert, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird, mit der Ausnahme, dass der zugrunde liegende Typ nicht größer sein darf als es intsei denn, der Wert eines Aufzählers kann nicht in ein intoder passen unsigned int. Wenn die Enumerator-Liste leer ist, ist der zugrunde liegende Typ so, als hätte die Enumeration einen einzelnen Enumerator mit dem Wert 0. Der Wert, der sizeof()auf einen Aufzählungstyp, ein Objekt vom Aufzählungstyp oder ein Enumerator sizeof()angewendet wird , ist der Wert von , der auf den angewendet wird zugrunde liegender Typ.

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


29
-1. Ihre Argumentation kann nicht korrekt sein - andernfalls, warum dürfen Sie "Klasse C" vorwärts deklarieren? und deklarieren Sie dann einen Funktionsprototyp, der ein C annimmt oder zurückgibt, bevor Sie C?
j_random_hacker

112
@j_random: Sie können eine Klasse nicht verwenden, bevor sie vollständig definiert ist. Sie können nur einen Zeiger oder einen Verweis auf diese Klasse verwenden. Dies liegt daran, dass ihre Größe und Funktionsweise nicht von der Klasse abhängt.
RnR

27
Die Größe einer Referenz oder eines Zeigers auf ein Klassenobjekt wird vom Compiler festgelegt und ist unabhängig von der tatsächlichen Größe des Objekts - es ist die Größe von Zeigern und Referenzen. Die Aufzählung ist ein Objekt und ihre Größe wird benötigt, damit der Compiler auf den richtigen Speicher zugreifen kann.
KJAWolf

16
Logischerweise wäre es möglich, Zeiger / Verweise auf Aufzählungen zu deklarieren, wenn wir vorwärts deklarierende Aufzählungen hätten, genau wie wir es mit Klassen tun können. Es ist nur so, dass Sie nicht oft mit Zeigern auf Aufzählungen zu tun haben :)
Pavel

20
Ich weiß, dass diese Diskussion vor langer Zeit beendet wurde, aber ich muss mich hier mit @j_random_hacker anstellen: Das Problem hier ist nicht der Zeiger oder Verweis auf unvollständige Typen, sondern die Verwendung unvollständiger Typen in Deklarationen. Da ist es legal zu tunstruct S; void foo(S s); (beachten Sie, dass dies foonur deklariert und nicht definiert ist), gibt es keinen Grund, warum wir dies nicht auch tun könnten enum E; void foo(E e);. In beiden Fällen wird die Größe nicht benötigt.
Luc Touraille

198

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:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.

1
Gibt es Compiler-Unterstützung für diese Funktion? GCC 4.5 scheint es nicht zu haben :(
rubenvb


Ich habe nach enum32_t gesucht und mit Ihrer Antwort enum XXX: uint32_t {a, b, c};
fantastisch

Ich dachte, Enumes mit Gültigkeitsbereich (Enum-Klasse) wurden in C ++ 11 implementiert. Wenn ja, wie sind sie dann in C ++ 0X legal?
Terrabits

1
C ++ 0x war der Arbeitsname für C ++ 11, @Terrabits, bevor es offiziell standardisiert wurde. Die Logik ist, dass, wenn bekannt ist (oder höchstwahrscheinlich), dass eine Funktion in einem aktualisierten Standard enthalten ist, die Verwendung dieser Funktion vor der offiziellen Veröffentlichung des Standards in der Regel den Arbeitsnamen verwendet. (ZB hatten Compiler, die C ++ 11-Funktionen vor der offiziellen Standardisierung im Jahr 2011 unterstützten, C ++ 0x-Unterstützung, Compiler, die C ++ 17-Funktionen vor der offiziellen Standardisierung unterstützten, C ++ 1z-Unterstützung und Compiler, die C ++ 20-Funktionen unterstützten Im Moment (2019) wird C ++ 2a unterstützt.)
Justin Time - Monica

79

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:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

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++0xoder -std=c++11in 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.


4
+1, da dies die einzige Antwort ist, in der erwähnt wird, dass Sie den Typ in Ihrer Deklaration sowie Ihre Definition deklarieren müssen.
Turoni

Ich glaube, die teilweise Unterstützung in frühen MSVC wurde von C ++ / CLIs enum classals C ++ - Erweiterung (bevor C ++ 11 anders war enum 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 keine enum classAufzä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.
Justin Time - Stellen Sie Monica

30

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 enumin C ++ deklarieren ?

Nein, kannst du nicht.

Aber warum nicht zulassen? Wenn es erlaubt wäre, könnten Sie Ihren enumTyp in Ihrer Header-Datei und Ihre enumWerte in Ihrer Quelldatei definieren. Klingt so, als ob es erlaubt sein sollte, oder?

Falsch.

In C ++ gibt es keinen Standardtyp für enumC # (int). In C ++ wird Ihr enumTyp vom Compiler als ein beliebiger Typ festgelegt, der dem Wertebereich entspricht, den Sie für Ihren haben enum.

Was bedeutet das?

Dies bedeutet, dass der enumzugrunde liegende Typ Ihres Unternehmens erst dann vollständig bestimmt werden kann, wenn Sie alle Werte des enumdefinierten Typs haben . Welchen Mann können Sie die Erklärung und Definition Ihrer nicht trennen enum. Und deshalb können Sie eine enumin C ++ nicht weiterleiten .

Der ISO C ++ Standard S7.2.5:

Der zugrunde liegende Typ einer Aufzählung ist ein integraler Typ, der alle in der Aufzählung definierten Aufzählungswerte darstellen kann. Es ist implementierungsdefiniert, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird, mit der Ausnahme, dass der zugrunde liegende Typ nicht größer sein darf als es intsei denn, der Wert eines Aufzählers kann nicht in ein intoder passen unsigned int. Wenn die Enumerator-Liste leer ist, ist der zugrunde liegende Typ so, als hätte die Enumeration einen einzelnen Enumerator mit dem Wert 0. Der Wert, der sizeof()auf einen Aufzählungstyp, ein Objekt vom Aufzählungstyp oder ein Enumerator sizeof()angewendet wird , ist der Wert von , der auf den angewendet wird zugrunde liegender Typ.

Sie können die Größe eines Aufzählungstyps in C ++ mithilfe des sizeofOperators 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 verwendet enum.

Was ist, wenn Sie den Typ Ihres enumexplizit so angeben :

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Können Sie dann Ihre weiterleiten enum?

Aber warum nicht?

Die Angabe des Typs von enumist nicht Teil des aktuellen C ++ - Standards. Es ist eine VC ++ - Erweiterung. Es wird jedoch Teil von C ++ 0x sein.

Quelle


14
Diese Antwort ist nun einige Jahre veraltet.
Tom

Die Zeit macht uns alle zum Narren. Ihr Kommentar ist nun einige Jahre veraltet. die Antwort ein Jahrzehnt!
pjcard

14

[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.


2
-1. Wenn Ihre Argumentation korrekt wäre, würde dieselbe Argumentation bedeuten, dass Vorwärtsdeklarationen von Klassentypen nicht zum Erstellen von Zeigern auf diese Typen verwendet werden könnten - aber sie können.
j_random_hacker

6
+1. Die Argumentation ist richtig. Der spezielle Fall sind Plattformen, bei denen sizeof (char *)> sizeof (int *). Beide können je nach Bereich zugrunde liegende Typen für eine Aufzählung sein. Klassen haben keine zugrunde liegenden Typen, daher ist die Analogie falsch.
MSalters

3
@MSalters: Beispiel: "struct S {int x;};" Nun, sizeof (S *) müssen auf die Größe eines anderen Zeiger-to-struct gleich sein, da C ++ ermöglicht ein solcher Zeiger deklariert werden und vor der Definition von S verwendet ...
j_random_hacker

1
@MSalters: ... Auf einer Plattform, auf der sizeof (char *)> sizeof (int *) die Verwendung eines solchen Zeigers in voller Größe für diese bestimmte Struktur möglicherweise ineffizient ist, vereinfacht dies jedoch die Codierung erheblich - und genau dasselbe etwas könnte und sollte für Aufzählungstypen getan werden.
j_random_hacker

4
Zeiger auf Daten und Zeiger auf Funktionen können unterschiedlich groß sein, aber ich bin mir ziemlich sicher, dass Datenzeiger einen Roundtrip ausführen müssen (in einen anderen Datenzeigertyp umgewandelt, dann zurück zum Original, müssen noch funktionieren), was impliziert, dass alle Datenzeiger sind gleich groß.
Ben Voigt

7

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.


5

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

enum X;

(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 :

enum X : int;

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.2im aktuellen Arbeitsentwurf.


4

Ich würde es so machen:

[im öffentlichen Header]

typedef unsigned long E;

void Foo(E e);

[im internen Header]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

Durch Hinzufügen von FORCE_32BIT stellen wir sicher, dass Econtent zu einem langen kompiliert wird, sodass es mit E austauschbar ist.


1
Dies bedeutet natürlich, dass (A) die Typen von E und Econtent unterschiedlich sind und (B) bei LP64-Systemen sizeof (E) = 2 * sizeof (EContent). Trivial Fix: ULONG_MAX, auch leichter zu lesen.
MSalters

2

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:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Anschließend deklarieren Sie in Ihrer Implementierungsdatei (cpp) eine Klasse, die die Interna darstellt.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

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:

((AImpl*)pImpl)->PrivateMethod();

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.


3
struct AImpl; struct A {privat: AImpl * pImpl; };

2

Sie können die Aufzählung in eine Struktur einschließen, einige Konstruktoren und Typkonvertierungen hinzufügen und stattdessen die Struktur weiter deklarieren.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Dies scheint zu funktionieren: http://ideone.com/TYtP2


1

Scheint, dass es in GCC nicht vorwärts deklariert werden kann!

Interessante Diskussion hier


1

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 ein integraler Typ, der alle in der Aufzählung definierten Aufzählungswerte darstellen kann. Es ist implementierungsdefiniert, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird, mit der Ausnahme, dass der zugrunde liegende Typ nicht größer als int sein darf, es sei denn, der Wert eines Aufzählers kann nicht in ein int oder ein vorzeichenloses int passen. Wenn die Aufzählerliste leer ist, ist der zugrunde liegende Typ so, als hätte die Aufzählung einen einzelnen Aufzähler mit dem Wert 0. Der Wert von sizeof (), der auf einen Aufzählungstyp, ein Objekt vom Aufzählungstyp oder einen Aufzähler angewendet wird, ist der Wert von sizeof () wird auf den zugrunde liegenden Typ angewendet.

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:

Eine Klasse, die deklariert, aber nicht definiert wurde, oder ein Array unbekannter Größe oder unvollständigen Elementtyps, ist ein unvollständig definierter Objekttyp.

Ein Klassentyp (z. B. "Klasse X") ist möglicherweise an einer Stelle in einer Übersetzungseinheit unvollständig und wird später vervollständigt. Der Typ "Klasse X" ist an beiden Punkten der gleiche Typ. Der deklarierte Typ eines Array-Objekts kann ein Array mit unvollständigem Klassentyp und daher unvollständig sein. Wenn der Klassentyp später in der Übersetzungseinheit vervollständigt wird, wird der Array-Typ vollständig. Der Array-Typ an diesen beiden Punkten ist der gleiche Typ. Der deklarierte Typ eines Array-Objekts kann ein Array unbekannter Größe sein und daher an einer Stelle in einer Übersetzungseinheit unvollständig sein und später vervollständigt werden. Die Array-Typen an diesen beiden Punkten ("Array mit unbekannter Grenze von T" und "Array mit N T") sind unterschiedliche Typen. Der Typ eines Zeigers auf ein Array unbekannter Größe.

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.


Beachten Sie, dass "enum foo" ausdrücklich nicht zulässig ist. in 7.1.5.3/1 (aber wie bei allem, solange der Compiler warnt, kann er diesen Code natürlich noch kompilieren)
Johannes Schaub - litb

Vielen Dank für den Hinweis, das ist ein wirklich esoterischer Absatz, und ich könnte eine Woche brauchen, um ihn zu analysieren. Aber es ist gut zu wissen, dass es da ist.
Dan Olson

Keine Sorge. Einige Standardabsätze sind wirklich seltsam :) Nun, ein ausgearbeiteter Typspezifizierer ist etwas, bei dem Sie einen Typ angeben, aber auch etwas mehr, um ihn eindeutig zu machen. zB "struct X" anstelle von "X" oder "enum Y" anstelle von nur "Y". Sie benötigen es, um zu behaupten, dass etwas wirklich ein Typ ist.
Johannes Schaub - litb

Sie können es also folgendermaßen verwenden: "class X * foo;" wenn X noch nicht vorwärts deklariert wurde. oder "Typname X :: foo" in einer Vorlage zur Begriffsklärung. oder "class link obj;" Wenn es eine Funktion "link" im selben Bereich gibt, die die Klasse mit demselben Namen beschatten würde.
Johannes Schaub - litb

In 3.4.4 heißt es, dass sie verwendet werden, wenn ein Name ohne Typ einen Typnamen verbirgt. Dort werden sie am häufigsten verwendet, abgesehen von der Vorwärtserklärung wie "Klasse X". (hier handelt es sich ausschließlich um eine Erklärung). es spricht hier in Nicht-Vorlagen darüber. In 14.6 / 3 wird jedoch eine Verwendung in Vorlagen aufgeführt.
Johannes Schaub - litb

1

Für VC ist hier der Test zur Vorwärtsdeklaration und Angabe des zugrunde liegenden Typs:

  1. Der folgende Code ist in Ordnung kompiliert.
    typedef int myint;
    Aufzählung T;
    void foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }}
    Aufzählung T: char
    {
        EIN
    };

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'

  1. VC (Microsoft (R) 32-Bit-C / C ++ - Optimierungscompiler Version 15.00.30729.01 für 80x86) sieht im obigen Fall fehlerhaft aus:

    • wenn man Enum T sieht; VC geht davon aus, dass der Aufzählungstyp T standardmäßig 4 Byte int als zugrunde liegenden Typ verwendet. Der generierte Assemblycode lautet also:
    foo @@ YAXPAW4T @@@ Z PROC; foo
    ;; Datei e: \ work \ c_cpp \ cpp_snippet.cpp
    ;; Zeile 13
        ebp drücken
        mov ebp, esp
    ;; Zeile 14
        mov eax, DWORD PTR _tp $ [ebp]
        mov DWORD PTR [eax], 305419896; 12345678H
    ;; Zeile 15
        Pop Ebp
        ret 0
    foo @@ YAXPAW4T @@@ Z ENDP; foo

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:

    int main (int argc, char * argv)
    {
        Gewerkschaft {
            char ca [4];
            T t;
        }ein;
        a.ca [0] = a.ca [1] = a. [ca [2] = a.ca [3] = 1;
        foo (& a.t);
        printf ("% # x,% # x,% # x,% # x \ n", a.ca [0], a.ca [1], a.ca [2], a.ca [3]) ;;
        return 0;
    }}

Das Ergebnis ist: 0x78, 0x56, 0x34, 0x12

  • Nachdem Sie die Vorwärtsdeklaration von Enum T entfernt und die Definition der Funktion foo nach der Definition von Enum T verschoben haben, ist das Ergebnis OK:

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.


1

In meinen Projekten habe ich die Namespace-Bound-Enumeration- Technik angewendet, um mit enums aus Legacy- und Drittanbieter-Komponenten umzugehen . Hier ist ein Beispiel:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Beachten Sie, dass der foo.hHeader nichts wissen muss legacy::evil. Nur die Dateien, die den Legacy-Typ legacy::evil(hier: main.cc) verwenden, müssen enthalten sein enum.h.


0

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):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

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:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

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.


-1

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.


-1

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.


1
Nein, nachfolgender Code muss die Werte nicht kennen, damit dies nützlich ist. Insbesondere wenn der nachfolgende Code lediglich ein Funktionsprototyp ist, der Enum-Parameter verwendet oder zurückgibt, ist die Größe des Typs nicht wichtig. Durch die Verwendung der Vorwärtsdeklaration können Buildabhängigkeiten entfernt und die Kompilierung beschleunigt werden.
j_random_hacker

Du hast recht. Die Absicht ist nicht, den Werten zu gehorchen, sondern dem Typ. Gelöst mit 0x Aufzählungstypen.
xtofl
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.