it-swarm-eu.dev

Forward-Deklaration einer Enumeration in C ++

Ich versuche etwas wie das Folgende 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 mir jemand erklären?

Erläuterung 2: Ich mache das, weil ich private Methoden in einer Klasse habe, die diese Aufzählung verwenden, und ich möchte nicht, dass die Werte der Aufzählung offengelegt werden - also möchte ich zum Beispiel, dass niemand weiß, dass E definiert ist als

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

da Projekt X nichts ist, was meine Benutzer wissen sollen.

Daher wollte ich die Aufzählung weiterleiten, 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 die Benutzer verteilen konnte.

Was den Compiler betrifft - es ist GCC.

253
szevvy

Der Grund dafür, dass die Aufzählung nicht weiter deklariert werden kann, ist, dass der Compiler den für die Aufzählungsvariable erforderlichen Speicher nicht kennt, ohne die Werte zu kennen. 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 Forward-Deklaration sichtbar ist, kann die Übersetzungseinheit nicht wissen, welche Speichergröße gewä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 ganzzahliger Typ, der alle in der Aufzählung definierten Enumeratorwerte darstellen kann. Es ist implementierungsdefiniert, welcher ganzzahlige Typ 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 passt nicht in ein int oder unsigned int. Wenn die Enumerator-Liste leer ist, ist der zugrunde liegende Typ so, als ob die Enumeration einen einzelnen Enumerator mit dem Wert 0 hätte. Der Wert von sizeof(), angewendet auf einen Aufzählungstyp, ein Objekt des Aufzählungstyps oder einen Enumerator, ist der Wert von sizeof(), angewendet auf den zugrunde liegenden 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 zuvor bekannt sein der Funktionsprototyp.

Update: In C++ 0X wurde eine Syntax für die Deklaration von Aufzählungstypen vorgeschlagen und akzeptiert. Sie finden den Vorschlag unter http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf

206
KJAWolf

Die Vorwärtsdeklaration von Aufzählungen ist auch in C++ 0x möglich. Bisher konnte kein Aufzählungstyp angegeben werden, da die Größe der Aufzählung vom Inhalt abhängt. Solange die Größe der Aufzählung von der Anwendung festgelegt wird, kann Folgendes 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.
188
user119017

Angesichts der jüngsten Entwicklungen füge ich hier eine aktuelle Antwort hinzu.

Sie können eine Enumeration in C++ 11 weiterleiten, sofern Sie gleichzeitig den 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, ist zu diesem Zeitpunkt überhaupt keine vollständige Deklaration erforderlich.

Dies wird von G ++ 4.6 und höher unterstützt (-std=c++0x oder -std=c++11 in neueren Versionen). Visual C++ 2013 unterstützt dies. In früheren Versionen gibt es eine Art von nicht standardmäßiger Unterstützung, die ich noch nicht herausgefunden habe. Ich fand einen Vorschlag, dass eine einfache Forward-Deklaration legal ist, aber YMMV.

73
Tom

Das Forward-Deklarieren von Dingen in C++ ist sehr nützlich, da es die Kompilierungszeit wird drastisch verkürzt . Sie können in C++ mehrere Dinge weiterleiten, einschließlich: struct, class, function, etc ...

Aber können Sie ein enum in C++ weiterleiten?

Nein, kannst du nicht.

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

Falsch.

In C++ gibt es keinen Standardtyp für enum wie in C # (int). In C++ wird Ihr enum -Typ vom Compiler als jeder Typ festgelegt, der in den Wertebereich Ihres enum passt.

Was bedeutet das?

Dies bedeutet, dass der zugrunde liegende Typ Ihres enum erst dann vollständig bestimmt werden kann, wenn Sie alle Werte des enum definiert haben. Mit welchem ​​Mann können Sie die Deklaration und Definition Ihres enum nicht trennen? Und deshalb können Sie ein enum in C++ nicht weiterleiten.

Der ISO C++ Standard S7.2.5:

Der zugrunde liegende Typ einer Enumeration ist ein ganzzahliger Typ, der alle in der Enumeration definierten Enumeratorwerte darstellen kann. Es ist implementierungsdefiniert, welcher ganzzahlige Typ 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 passt nicht in ein int oder unsigned int. Wenn die Aufzählungsliste leer ist, ist der zugrunde liegende Typ so, als ob die Aufzählung einen einzelnen Aufzähler mit dem Wert 0 hätte. Der Wert von sizeof() wird auf einen Aufzählungstyp, ein Objekt des Aufzählungstyps oder einen Aufzählungstyp angewendet. ist der Wert von sizeof(), der auf den zugrunde liegenden Typ angewendet wird.

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

Was ist, wenn Sie den Typ Ihres enum explizit wie folgt angeben:

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

Können Sie dann Ihr enum weiterleiten?

Nein, aber warum nicht?

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

Quelle

30
Brian R. Bondy

[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 Zeiger auf verschiedene Aufzählungstypen nicht garantiert dieselbe Größe haben. Der Compiler muss möglicherweise die Definition sehen, 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 konsistente Größe. Die Forward-Deklaration von Enums wird beispielsweise von Visual C++ als Spracherweiterung bereitgestellt.

13
James Hopkin

Es gibt in der Tat keine vordefinierte 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 für private Mitglieder verwendet wird, können Sie die Kapselung implementieren, indem Sie die Aufzählung selbst als privates Mitglied dieser Klasse definieren. Die Enumeration muss zum Zeitpunkt der Deklaration, dh innerhalb der Klassendefinition, noch vollständig definiert werden. Dies ist jedoch kein größeres Problem, da dort private Mitgliederfunktionen deklariert werden, und es handelt sich auch nicht um eine schlechtere Darstellung von Implementierungsinternalen.

Wenn Sie eine tiefere Verschleierung Ihrer 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 Member-Funktion der Schnittstelle ausgeführt werden. Auf diese Weise wird nicht einmal der wirkliche Klassenname, geschweige denn seine privaten Funktionen enthüllt.

7

Ich stelle nur fest, dass der Grund tatsächlich ist ist, dass die Größe der Aufzählung nach der Vorwärtsdeklaration noch nicht bekannt ist. Nun, Sie verwenden die Forward-Deklaration einer Struktur, um einen Zeiger herumzuleiten oder auf ein Objekt von einer Stelle aus zu verweisen, auf die auch in der Forward-Deklarationsstrukturdefinition 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 ich kürzlich erfahren habe, dass einige Plattformen Zeiger mit einer anderen 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 explizit das Ausführen von so etwas

enum X;

(im 7.1.5.3/1). Aber der nächste C++ Standard für nächstes Jahr lässt folgendes zu, was mich eigentlich vom Problem überzeugt hat hat mit dem zugrunde liegenden Typ zu tun:

enum X : int;

Es ist als "undurchsichtige" Enum-Deklaration 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 Aufzählung definiert werden. Sehen 7.2 im aktuellen Arbeitsentwurf.

Ich würde es so machen:

[in der öffentlichen Kopfzeile]

typedef unsigned long E;

void Foo(E e);

[im internen Header]

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

Indem wir FORCE_32BIT hinzufügen, stellen wir sicher, dass Econtent zu lange kompiliert und mit E austauschbar ist.

4
Laurie Cheers

Es scheint, dass es nicht in GCC weiter deklariert werden kann!

Interessante Diskussion hier

2
prakash

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

2
Leszek Swirski

Wenn Sie wirklich nicht möchten, dass Ihre Aufzählung in Ihrer Header-Datei erscheint UND Sie sicherstellen, dass sie nur von privaten Methoden verwendet wird, kann eine Lösung das Pimpl-Prinzip sein.

Es ist eine Technik, die sicherstellt, dass die Klasseninternale in den Überschriften verborgen werden, indem man einfach erklärt:

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

Dann deklarieren Sie in Ihrer Implementierungsdatei (cpp) eine Klasse, die die Repräsentation der 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 Profis für die Verwendung von pimpl. Zum einen wird der Klassenheader von der Implementierung entkoppelt, sodass beim Ändern einer Klassenimplementierung keine weiteren Klassen kompiliert werden müssen. Ein weiterer Grund ist, dass sich die Kompilierungszeit verkürzt, da Ihre Header so einfach sind.

Aber es ist ein Schmerz zu benutzen, deshalb sollten Sie sich wirklich fragen, ob es so viel Mühe bereitet, Ihre Enumeration als privat in der Kopfzeile zu deklarieren.

2
Vincent Robert

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 Header foo.h Nichts über legacy::evil Wissen muss. Nur die Dateien, die den alten Typ legacy::evil (Hier: main.cc) verwenden, müssen enum.h Enthalten.

1
mavam

Für VC ist hier der Test zur Forward-Deklaration und zur Angabe des zugrunde liegenden Typs:

  1. der folgende Code ist in Ordnung kompiliert.
 typedef int myint; 
 enum T; 
 void foo (T * tp) 
 {
 * tp = (T) 0x12345678; 
} 
 enum T: char 
 {
 A 
}; 

Habe aber die Warnung für/W4 (/ W3 hat diese Warnung nicht)

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 Assembly-Code lautet also:
? foo @@ YAXPAW4T @@@ Z PROC; foo 
; Datei e:\work\c_cpp\cpp_snippet.cpp 
; Zeile 13 
 Push ebp 
 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 Assembly-Code wird direkt aus /Fatest.asm extrahiert, nicht meine persönliche Vermutung. Sehen Sie den Film DWORD PTR [eax], 305419896; 12345678H Leitung?

das folgende Code-Snippet beweist es:

 int main (int argc, char * argv) 
 {
 union {
 char ca [4]; 
 T t; 
} a; 
 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

  • nach Entfernen der Forward-Deklaration von Enum T und Verschieben der Definition der Funktion foo nach der Definition von Enum T: Das Ergebnis ist OK:

die obige Schlüsselanweisung wird:

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 gilt daher als schädlich.

Übrigens ist die Syntax für die Deklaration des zugrunde liegenden Typs dieselbe wie in C #. In der Praxis habe ich festgestellt, dass es sich lohnt, 3 Bytes zu sparen, indem der zugrunde liegende Typ als Zeichen angegeben wird, wenn mit dem eingebetteten System gesprochen wird, das speicherbeschränkt ist.

1
zhaorufei

Es gibt einige Meinungsverschiedenheiten, da diese (irgendwie) bestoßen wurden, also hier einige relevante Teile aus dem Standard. Untersuchungen haben gezeigt, dass der Standard weder eine Forward-Deklaration definiert, noch ausdrücklich angibt, dass Enums Forward-Deklarationen möglich oder nicht möglich sind.

Zunächst aus dcl.enum, Abschnitt 7.2:

Der zugrunde liegende Typ einer Enumeration ist ein ganzzahliger Typ, der alle in der Enumeration definierten Enumeratorwerte darstellen kann. Es ist implementierungsdefiniert, welcher ganzzahlige Typ 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 Enumerators kann nicht in ein int oder ein vorzeichenloses int passen. Wenn die Aufzählungsliste leer ist, ist der zugrunde liegende Typ so, als hätte die Aufzählung einen einzelnen Aufzählungswert mit dem Wert 0. Der Wert von sizeof (), der auf einen Aufzählungstyp, ein Objekt des Aufzählungstyps oder einen Aufzählungstyp 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 gehen wir zu dem Abschnitt über "unvollständige Typen" (3.9) über, der in etwa so ähnlich ist, wie wir es mit einem Standard für Forward-Deklarationen zu tun haben:

Eine deklarierte, aber nicht definierte Klasse oder ein Array unbekannter Größe oder eines unvollständigen Elementtyps ist ein unvollständig definierter Objekttyp.

Ein Klassentyp (z. B. "Klasse X") kann an einer Stelle in einer Übersetzungseinheit unvollständig sein und später vervollständigt werden. Der Typ "Klasse X" ist an beiden Stellen 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 vervollständigt. Der Array-Typ an diesen beiden Punkten ist derselbe 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 NT") sind unterschiedliche Typen. Der Typ eines Zeigers auf ein Array unbekannter Größe oder eines Typs, der durch eine typedef-Deklaration als Array unbekannter Größe definiert ist, kann nicht vervollständigt werden.

Dort hat der Standard also ziemlich genau die Typen festgelegt, die vorwärts deklariert werden können. Da Enum nicht vorhanden war, betrachten Compiler-Autoren die Forward-Deklaration aufgrund der variablen Größe des zugrunde liegenden Typs im Allgemeinen als vom Standard nicht zulässig.

Das macht auch Sinn. Auf Aufzählungen wird normalerweise in By-Value-Situationen verwiesen, und der Compiler müsste in solchen Situationen tatsächlich die Speichergröße 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 dann weiter deklariert werden. Ein interessantes Experiment könnte darin bestehen, eine Enumeration in Visual Studio 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.

1
Dan Olson

Meine Lösung für Ihr Problem wäre entweder:

1 - Verwenden Sie int anstelle von enums: 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önnten 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 Konstanteninstanzen, wie in Java. Deklarieren Sie die Klasse vorwärts, definieren Sie sie in der CPP-Datei und instanziieren Sie nur die enum-ähnlichen Werte. Ich habe so etwas in C++ gemacht, und das Ergebnis war nicht so zufriedenstellend wie gewünscht, da es Code zur Simulation einer Aufzählung (Kopierkonstruktion, operator = usw.) benötigte.

3: Verwenden Sie wie zuvor vorgeschlagen die privat deklarierte Enumeration. Obwohl ein Benutzer die vollständige Definition sehen wird, 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.

0
paercebal