it-swarm.it

Inoltra dichiarando un enum in C ++

Sto cercando di fare qualcosa di simile al seguente:

enum E;

void Foo(E e);

enum E {A, B, C};

che il compilatore rifiuta. Ho dato una rapida occhiata a Google e il consenso sembra essere "non puoi farlo", ma non riesco a capire perché. Qualcuno può spiegare?

Chiarimento 2: lo sto facendo poiché ho metodi privati ​​in una classe che accetta detto enum e non voglio che i valori dell'enum siano esposti, quindi, per esempio, non voglio che nessuno sappia che E è definito come

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

poiché il progetto X non è qualcosa che voglio che i miei utenti sappiano.

Quindi, volevo inoltrare l'enum in modo da poter mettere i metodi privati ​​nel file di intestazione, dichiarare l'enum internamente nel cpp e distribuire il file della libreria e l'intestazione creati alle persone.

Per quanto riguarda il compilatore, è GCC.

248
szevvy

Il motivo per cui l'enum non può essere dichiarato in avanti è che senza conoscere i valori, il compilatore non può conoscere l'archiviazione richiesta per la variabile enum. I compilatori C++ possono specificare lo spazio di archiviazione effettivo in base alle dimensioni necessarie per contenere tutti i valori specificati. Se tutto ciò che è visibile è la dichiarazione in avanti, l'unità di traduzione non può sapere quale dimensione di archiviazione sarà stata scelta - potrebbe essere un carattere o un int o qualcos'altro.


Dalla sezione 7.2.5 della norma ISO C++:

Il tipo sottostante di un'enumerazione è un tipo integrale che può rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione. Viene definito l'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione, tranne per il fatto che il tipo sottostante non deve essere maggiore di int a meno che il valore di un enumeratore non possa rientrare in int o unsigned int. Se l'elenco dell'enumeratore è vuoto, il tipo sottostante è come se l'enumerazione avesse un singolo enumeratore con valore 0. Il valore di sizeof() applicato a un'enumerazione type, un oggetto di tipo enumerazione o un enumeratore, è il valore di sizeof() applicato al tipo sottostante.

Poiché il chiamante della funzione deve conoscere le dimensioni dei parametri per impostare correttamente lo stack di chiamate, il numero di enumerazioni in un elenco di enumerazioni deve essere noto prima il prototipo di funzione.

Aggiornamento: in C++ 0X è stata proposta e accettata una sintassi per la dichiarazione anticipata dei tipi di enum. Puoi vedere la proposta su http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf

201
KJAWolf

La dichiarazione diretta degli enum è possibile anche in C++ 0x. In precedenza, il motivo per cui i tipi di enum non potevano essere dichiarati in avanti è perché la dimensione dell'enumerazione dipende dal suo contenuto. Finché la dimensione dell'enumerazione è specificata dall'applicazione, può essere dichiarata in avanti:

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.
187
user119017

Sto aggiungendo una risposta aggiornata qui, dati i recenti sviluppi.

È possibile dichiarare in avanti un'enum in C++ 11, purché si dichiari contemporaneamente il suo tipo di archiviazione. La sintassi è simile al seguente:

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

....

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

In effetti, se la funzione non fa mai riferimento ai valori dell'enumerazione, in quel momento non è necessaria la dichiarazione completa.

Questo è supportato da G ++ 4.6 e versioni successive (-std=c++0x o -std=c++11 nelle versioni più recenti). Visual C++ 2013 supporta questo; nelle versioni precedenti ha una sorta di supporto non standard che non ho ancora capito - ho trovato qualche suggerimento che una semplice dichiarazione in avanti è legale, ma YMMV.

72
Tom

Dichiarare in avanti le cose in C++ è molto utile perché accelera notevolmente i tempi di compilazione . Puoi inoltrare diverse cose in C++ tra cui: struct, class, function, ecc ...

Ma puoi inoltrare un enum in C++?

No non puoi.

Ma perché non permetterlo? Se fosse consentito, è possibile definire il tipo enum nel file di intestazione e i valori enum nel file di origine. Sembra che dovrebbe essere permesso giusto?

Sbagliato.

In C++ non esiste un tipo predefinito per enum come in C # (int). In C++ il tuo tipo enum sarà determinato dal compilatore come qualsiasi tipo che si adatti all'intervallo di valori che hai per il tuo enum.

Cosa significa?

Significa che il tipo sottostante del tuo enum non può essere completamente determinato fino a quando non hai definito tutti i valori di enum. Quale mans non puoi separare la dichiarazione e la definizione del tuo enum. E quindi non è possibile inoltrare un enum in C++.

Lo standard ISO C++ S7.2.5:

Il tipo sottostante di un'enumerazione è un tipo integrale che può rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione. Viene definito l'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione, tranne per il fatto che il tipo sottostante non deve essere maggiore di int a meno che il valore di un enumeratore non possa rientrare in int o unsigned int. Se l'elenco di enumeratori è vuoto, il tipo sottostante è come se l'enumerazione avesse un unico enumeratore con valore 0. Il valore di sizeof() applicato a un tipo di enumerazione, un oggetto di tipo enumerazione o un enumeratore, è il valore di sizeof() applicato al tipo sottostante.

È possibile determinare la dimensione di un tipo enumerato in C++ utilizzando l'operatore sizeof. La dimensione del tipo elencato è la dimensione del tipo sottostante. In questo modo puoi indovinare quale tipo sta usando il tuo compilatore per il tuo enum.

Cosa succede se si specifica esplicitamente il tipo di enum in questo modo:

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

Puoi quindi inoltrare il tuo enum?

No. Ma perché no?

La specifica del tipo di enum non fa effettivamente parte dell'attuale standard C++. È un'estensione VC++. Farà comunque parte di C++ 0x.

Source

30
Brian R. Bondy

[La mia risposta è sbagliata, ma l'ho lasciata qui perché i commenti sono utili].

Dichiarare in avanti enum non è standard, perché non è garantito che i puntatori a diversi tipi di enum abbiano le stesse dimensioni. Il compilatore potrebbe aver bisogno di vedere la definizione per sapere quali puntatori dimensione possono essere usati con questo tipo.

In pratica, almeno su tutti i compilatori popolari, i puntatori agli enumeratori hanno dimensioni coerenti. Visual C++, ad esempio, la dichiarazione forward di enum viene fornita come estensione del linguaggio.

13
James Hopkin

In effetti non esiste una dichiarazione di enum diretta. Poiché la definizione di un enum non contiene alcun codice che potrebbe dipendere da un altro codice che utilizza l'enum, di solito non è un problema definire completamente l'enum quando lo si dichiara per la prima volta.

Se l'unico uso del tuo enum è da parte di funzioni di membro privato, puoi implementare l'incapsulamento avendo l'enum stesso come membro privato di quella classe. L'enum deve ancora essere completamente definito nel punto di dichiarazione, cioè all'interno della definizione della classe. Tuttavia, questo non è un problema più grande in quanto dichiarare lì le funzioni di membri privati, e non è una peggiore esposizione degli interni di implementazione di quella.

Se hai bisogno di un grado di occultamento più profondo per i dettagli dell'implementazione, puoi suddividerlo in un'interfaccia astratta, composta solo da funzioni virtuali pure e una classe concreta, completamente nascosta, che implementa (eredita) l'interfaccia. La creazione di istanze di classe può essere gestita da una factory o da una funzione membro statica dell'interfaccia. In questo modo, anche il vero nome della classe, per non parlare delle sue funzioni private, non sarà esposto.

7

Solo notando che il motivo in realtà è che la dimensione dell'enum non è ancora nota dopo la dichiarazione in avanti. Bene, usi la dichiarazione diretta di una struttura per essere in grado di passare un puntatore o fare riferimento a un oggetto da un punto a cui fa riferimento anche la stessa definizione della struttura dichiarata in avanti.

Dichiarare in avanti un enum non sarebbe troppo utile, perché si vorrebbe poter passare l'enum per valore. Non potresti nemmeno avere un puntatore ad esso, perché recentemente mi è stato detto che alcune piattaforme usano puntatori di dimensioni diverse per char che per int o long. Quindi tutto dipende dal contenuto dell'enum.

L'attuale standard C++ non consente esplicitamente di fare qualcosa del genere

enum X;

(in 7.1.5.3/1). Ma il prossimo standard C++ dovuto al prossimo anno consente quanto segue, che mi ha convinto che il problema in realtà ha a che fare con il tipo sottostante:

enum X : int;

È conosciuta come una dichiarazione enum "opaca". Puoi anche usare X per valore nel seguente codice. E i suoi enumeratori possono essere successivamente definiti in una successiva redeclaration dell'enumerazione. Vedi 7.2 nella bozza di lavoro corrente.

Lo farei così:

[nell'intestazione pubblica]

typedef unsigned long E;

void Foo(E e);

[nell'intestazione interna]

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

Aggiungendo FORCE_32BIT ci assicuriamo che Econtent si compili in un lungo, quindi è intercambiabile con E.

4
Laurie Cheers

Sembra che non possa essere dichiarato in avanti in GCC!

Discussione interessante qui

2
prakash

È possibile racchiudere l'enum in una struttura, aggiungere alcuni costruttori e digitare conversioni e inoltrare invece la struttura.

#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; \
}

Questo sembra funzionare: http://ideone.com/TYtP2

2
Leszek Swirski

Se davvero non vuoi che il tuo enum appaia nel tuo file header e assicuri che sia usato solo da metodi privati, allora una soluzione può essere quella di seguire il principio pimpl.

È una tecnica che assicura di nascondere gli interni della classe nelle intestazioni dichiarando semplicemente:

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

Quindi nel tuo file di implementazione (cpp), dichiari una classe che sarà la rappresentazione degli interni.

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

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

È necessario creare in modo dinamico l'implementazione nel costruttore della classe ed eliminarla nel distruttore e quando si implementa il metodo pubblico, è necessario utilizzare:

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

Esistono dei vantaggi nell'uso di pimpl, uno è che disaccoppia l'intestazione della sua classe dalla sua implementazione, non è necessario ricompilare altre classi quando si cambia un'implementazione di una classe. Un altro è che accelera i tempi di compilazione perché le intestazioni sono così semplici.

Ma è un dolore da usare, quindi dovresti davvero chiederti se dichiarare che il tuo enum come privato nell'intestazione è un vero problema.

2
Vincent Robert

Nei miei progetti, ho adottato la tecnica Namespace-Bound Enumeration per gestire enums da componenti legacy e di terze parti. Ecco un esempio:

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;
}

Nota che l'intestazione foo.h non deve sapere nulla di legacy::evil. Solo i file che utilizzano il tipo legacy legacy::evil (qui: main.cc) devono includere enum.h.

1
mavam

Per VC, ecco il test sulla dichiarazione diretta e sulla specifica del tipo sottostante:

  1. il seguente codice è compilato ok.
 typedef int myint; 
 enum T; 
 void foo (T * tp) 
 {
 * tp = (T) 0x12345678; 
} 
 enum T: char 
 {
 A 
}; 

Ma ho ricevuto l'avviso per/W4 (/ W3 non ha questo avviso)

avviso C4480: estensione non standard utilizzata: specifica del tipo sottostante per enum 'T'

  1. VC (Microsoft (R) 32-bit ++ Ottimizzazione della versione del compilatore 15.00.30729.01 per 80x86) sembra difettoso nel caso precedente:

    • quando si vede enum T; VC presuppone che il tipo enum T utilizzi 4 byte predefiniti come tipo sottostante, quindi il codice assembly generato è:
? foo @@ YAXPAW4T @@@ Z PROC; foo 
; File e:\work\c_cpp\cpp_snippet.cpp 
; Riga 13 
 Push ebp 
 Mov ebp, esp 
; Riga 14 
 Mov eax, DWORD PTR _tp $ [ebp] 
 Mov DWORD PTR [eax], 305419896; 12345678H 
; Riga 15 
 Pop ebp 
 Ret 0 
? Foo @@ YAXPAW4T @@@ Z ENDP; foo 

Il suddetto codice Assembly viene estratto direttamente da /Fatest.asm, non è una mia ipotesi personale. Vedi il mov DWORD PTR [eax], 305419896; Linea 12345678H?

il seguente frammento di codice lo dimostra:

 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]); 
 restituisce 0; 
} 

il risultato è: 0x78, 0x56, 0x34, 0x12

  • dopo rimuovere la dichiarazione in avanti di enum T e spostare la definizione di funzione foo dopo la definizione di enum T: il risultato è OK:

le istruzioni chiave sopra diventano:

mov BYTE PTR [eax], 120; 00000078H

il risultato finale è: 0x78, 0x1, 0x1, 0x1

Si noti che il valore non viene sovrascritto

Quindi l'uso della dichiarazione forward di enum in VC è considerato dannoso.

A proposito, per non sorprendere, la sintassi per la dichiarazione del tipo sottostante è la stessa in C #. In pratica ho scoperto che vale la pena salvare 3 byte specificando il tipo sottostante come char quando si parla al sistema incorporato, che è limitato dalla memoria.

1
zhaorufei

C'è un po 'di dissenso da quando questo è stato urtato (una specie di), quindi ecco alcuni bit rilevanti dello standard. La ricerca mostra che lo standard non definisce realmente la dichiarazione a termine, né afferma esplicitamente che gli enum possono o non possono essere dichiarati a termine.

Innanzitutto, da dcl.enum, sezione 7.2:

Il tipo sottostante di un'enumerazione è un tipo integrale che può rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione. È definito dall'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione, tranne per il fatto che il tipo sottostante non deve essere maggiore di int a meno che il valore di un enumeratore non possa rientrare in un int o in un unsigned int. Se l'elenco di enumeratori è vuoto, il tipo sottostante è come se l'enumerazione avesse un singolo enumeratore con valore 0. Il valore di sizeof () applicato a un tipo di enumerazione, un oggetto di tipo di enumerazione o un enumeratore, è il valore di sizeof () applicato al tipo sottostante.

Quindi il tipo sottostante di un enum è definito dall'implementazione, con una limitazione minore.

Ora passiamo alla sezione sui "tipi incompleti" (3.9), che è più o meno vicina a qualsiasi standard sulle dichiarazioni a termine:

Una classe che è stata dichiarata ma non definita, o una matrice di dimensione sconosciuta o di tipo di elemento incompleto, è un tipo di oggetto definito in modo incompleto.

Un tipo di classe (come "classe X") potrebbe essere incompleto in un punto in un'unità di traduzione e completare in seguito; il tipo "classe X" è lo stesso tipo in entrambi i punti. Il tipo dichiarato di un oggetto array può essere un array di tipo di classe incompleto e quindi incompleto; se il tipo di classe viene completato in seguito nell'unità di traduzione, il tipo di matrice diventa completo; il tipo di array in questi due punti è dello stesso tipo. Il tipo dichiarato di un oggetto array può essere un array di dimensioni sconosciute e quindi essere incompleto in un punto in un'unità di traduzione e completare in seguito; i tipi di array in questi due punti ("array di limite sconosciuto di T" e "array di N T") sono tipi diversi. Il tipo di un puntatore a un array di dimensioni sconosciute o di un tipo definito da una dichiarazione typedef come array di dimensioni sconosciute non può essere completato.

Quindi, lo standard ha praticamente definito i tipi che possono essere dichiarati in avanti. Enum non c'era, quindi gli autori di compilatori generalmente considerano in avanti la dichiarazione non consentita dallo standard a causa delle dimensioni variabili del tipo sottostante.

Ha anche senso. In genere, gli enum sono referenziati in situazioni per valore, e il compilatore dovrebbe effettivamente conoscere la dimensione della memoria in quelle situazioni. Poiché la dimensione della memoria è definita dall'implementazione, molti compilatori possono semplicemente scegliere di usare valori a 32 bit per il tipo sottostante di ogni enum, a quel punto diventa possibile inoltrarli dichiarandoli. Un esperimento interessante potrebbe essere quello di provare a dichiarare un enum in Visual Studio, costringendolo quindi a utilizzare un tipo sottostante maggiore di sizeof (int) come spiegato sopra per vedere cosa succede.

1
Dan Olson

La mia soluzione al tuo problema sarebbe di:

1 - usa int invece di enums: dichiara i tuoi ints in uno spazio dei nomi anonimo nel tuo file CPP (non nell'intestazione):

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

Dato che i tuoi metodi sono privati, nessuno rovinerà i dati. Potresti anche andare oltre per testare se qualcuno ti invia dati non validi:

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: crea una classe completa con istanze const limitate, come fatto in Java. Dichiarare in avanti la classe, quindi definirla nel file CPP e istanziare solo i valori simili a enum. Ho fatto qualcosa del genere in C++ e il risultato non è stato soddisfacente come desiderato, in quanto aveva bisogno di un po 'di codice per simulare un enum (costruzione di copia, operatore =, ecc.).

3: Come proposto in precedenza, utilizzare l'enum dichiarato privatamente. Nonostante il fatto che un utente vedrà la sua definizione completa, non sarà in grado di usarlo, né utilizzare i metodi privati. Quindi di solito sarai in grado di modificare l'enum e il contenuto dei metodi esistenti senza bisogno di ricompilare il codice usando la tua classe.

La mia ipotesi sarebbe la soluzione 3 o 1.

0
paercebal