2009-11-26 10 views
16

ho incontrato questa situazione così tante volte ...C/C++: un modo per ottenere enumerazioni riflessive?

enum Fruit { 
    Apple, 
    Banana, 
    Pear, 
    Tomato 
}; 

ora ho Fruit f; // banana e voglio andare da f alla stringa "Banana"; oppure ho string s = "Banana" e da quello voglio andare a Banana // enum value or int.

Finora ho fatto questo .. Supponendo che l'enumerazione è in Fruit.h:

// Fruit.cpp 
const char *Fruits[] = { 
"Apple", 
"Banana", 
"Pear", 
"Tomato", 
NULL 
}; 

Ovviamente questa è una soluzione disordinata. Se uno sviluppatore aggiunge un nuovo frutto all'intestazione e non aggiunge una nuova voce in Fruits [] (non si può biasimarlo, devono essere in due file diversi!) L'applicazione va a gonfie vele.

C'è un modo semplice per fare ciò che voglio, dove tutto è in un unico file? Hack preprocessore, magia aliena, qualsiasi cosa ..

PS: Questo, contrariamente alla riflessione "per tutto", sarebbe davvero banale da implementare nei compilatori. Vedendo quanto sia un problema comune (almeno per me) non riesco davvero a credere che non ci sia il reflective enum Fruit .. Neanche in C++ 0x.

PS2: Sto usando C++ ma ho taggato questa domanda come C, perché C ha lo stesso problema. Se la tua soluzione include solo cose C++, va bene per me.

+2

Questo aspetto è molto simile a: http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c/ –

+0

@Jonathan: sì , anche se questo richiede una mappatura bidirezionale. E string-to-int è più difficile rispetto ai mapping int-to-string. – MSalters

+1

@ Mark: Scusa la mia ignoranza, ma cosa, biologicamente, è un pomodoro se non è un frutto nel tuo libro? – DevSolar

risposta

32

Questo richiede che i frutti siano definiti in un file esterno. Questo sarebbe il contenuto di fruit.cpp:

#define FRUIT(name) name 
enum Fruit { 
#include "fruit-defs.h" 
NUM_FRUITS 
}; 
#undef FRUIT 
#define FRUIT(name) #name 
const char *Fruits [] = { 
#include "fruit-defs.h" 
NULL 
}; 
#undef FRUIT 

E questo sarebbe frutta-defs.h:

FRUIT(Banana), 
FRUIT(Apple), 
FRUIT(Pear), 
FRUIT(Tomato), 

Funziona fino a quando i valori iniziano a 0 e sono consecutivi ...

Aggiornamento: mescolare questa soluzione con quella di Richard Pennington usando C99 se avete bisogno di non consecuti valori. Vale a dire, qualcosa come:

// This would be in fruit-defs.h 
FRUIT(Banana, 7) 
... 
// This one for the enum 
#define FRUIT(name, number) name = number 
.... 
// This one for the char *[] 
#define FRUIT(name, number) [number] = #name 
+1

Questa è una buona idea. –

+0

Non ho mai pensato di farlo in quel modo. – Eld

+0

Scavando questo vecchio post, mi dispiace per quello.Potresti spiegare cosa fanno NUM_FRUITS e NULL rispettivamente in Fruit and Fruits *? Perché è necessario, cosa fa e dove viene dichiarato NUM_FRUITS? – Mads

3

E se facessi qualcosa del genere?

enum Fruit { 
    Apple, 
    Banana, 
    NumFruits 
}; 

const char *Fruits[NumFruits] = { 
"Apple", 
"Banana", 
}; 

Poi se si aggiunge una nuova voce al enum frutta, il compilatore dovrebbe lamentano che ci sono voci sufficienti nel inizializzazione della matrice, in modo da sarebbe costretta a aggiungere una voce alla matrice.

Quindi protegge dall'avere la matrice della dimensione sbagliata, ma non aiuta a garantire che le stringhe siano corrette.

+1

Sì, ma 'const char * A [50] = {" A "};' funziona comunque. Quindi protegge solo dall'aggiunta di nuovi elementi a Fruit senza modificare l'array: se qualcuno elimina un Fruit dall'enumerazione o scambia due valori, l'array verrà desincronizzato. –

+1

Penso che dipenda da quale compilatore stai usando e da quali avvisi hai attivato. Guarda le opzioni del tuo compilatore e vedi se puoi abilitare un avviso per gli inizializzatori incompleti se non lo fa già. –

+0

Non conosco per certo alcun compilatore C/C++ che generi questo avviso ... quindi potrei sbagliarmi completamente. Se è così, allora è solo un pio desiderio. –

1

Come hanno mostrato le altre persone che rispondono alla domanda, non esiste un modo pulito ("D.R.Y.") per farlo usando il preprocessore C da solo. Il problema è che è necessario definire una matrice di dimensioni del proprio enum contenente stringhe corrispondenti a ciascun valore enum, e il preprocessore C non è abbastanza intelligente da essere in grado di farlo. Quello che faccio è creare un file di testo simile a questo:

%status ok 
%meaning 
The routine completed its work successfully. 
% 

%status eof_reading_content 
%meaning 

The routine encountered the end of the input before it expected 
to. 

% 

Qui delimitatori del segno%.

Poi uno script Perl, la parte di lavoro di cui si presenta così,

sub get_statuses 
{ 
    my ($base_name, $prefix) = @_; 
    my @statuses; 
    my $status_txt_file = "$base_name.txt"; 
    my $status_text = file_slurp ($status_txt_file); 
    while ($status_text =~ 
     m/ 
     \%status\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\n 
     \%meaning\s*(.*?)\s*\n\%\s*\n 
     /gxs) { 
    my ($code, $meaning) = ($1, $2); 
    $code = $prefix."_$code"; 
    $meaning =~ s/\s+/ /g; 
    push @statuses, [$code, $meaning]; 
    } 
    return @statuses; 
} 

legge questo file e scrive un file di intestazione:

typedef enum kinopiko_status { 
    kinopiko_status_ok, 
    kinopiko_status_eof_reading_content, 

e un file C:

/* Generated by ./kinopiko-status.pl at 2009-11-09 23:45. */ 
#include "kinopiko-status.h" 
const char * kinopiko_status_strings[26] = { 
"The routine completed its work successfully.", 
"The routine encountered the end of the input before it expected to. ", 

utilizzando il file di input nella parte superiore. Calcola anche il numero 26 contando le linee di input. (Ci sono ventisei stati possibili in effetti.)

Quindi la costruzione del file di stringa di stato viene automatizzata utilizzando make.

+0

Mi piacerebbe sapere perché questo è stato downvoted. Quanto sopra è tratto da un programma funzionante (solo il nome è cambiato!). –

+0

Penso che la tua risposta sarebbe molto più chiara se hai postato alcuni script Perl. In particolare, perché ci sono stringhe '% status' e '% meaning'? Cosa hanno a che fare con la domanda? Inoltre, il "kinopiko_" non è chiaro finché qualcuno non guarda il tuo nome utente. –

+0

Non c'è molto che posso fare a proposito dell'ultima lamentela, poiché se avessi postato il vero nome ciò significherebbe ancora meno per te. Ho pubblicato uno snippet del parser perl per questo. –

1

potrebbe fare una struttura di classe per esso:

class Fruit { 
    int value; char const * name ; 
    protected: 
    Fruit(int v, char const * n) : value(v), name(n) {} 
    public: 
    int asInt() const { return value ; } 
    char const * cstr() { return name ; } 
} ; 
#define MAKE_FRUIT_ELEMENT(x, v) class x : public Fruit { x() : Fruit(v, #x) {} } 

// Then somewhere: 
MAKE_FRUIT_ELEMENT(Apple, 1); 
MAKE_FRUIT_ELEMENT(Banana, 2); 
MAKE_FRUIT_ELEMENT(Pear, 3); 

allora si può avere una funzione che prende un frutto, e sarà anche essere più sicuri tipo.

void foo(Fruit f) { 
    std::cout << f.cstr() << std::endl; 
    switch (f.asInt()) { /* do whatever * } ; 
} 

La dimensione di questo è 2 volte più grande di un enum. Ma più che probabilmente non importa.

9

Un modo C99 che ho trovato aiuta a ridurre gli errori:

enum Fruit { 
    APPLE, 
    BANANA 
}; 
const char* Fruits[] = { 
[APPLE] = "APPLE", 
[BANANA] = "BANANA" 
}; 

È possibile aggiungere le enumerazioni, anche nel mezzo, e non rompere le vecchie definizioni. Puoi ancora ottenere stringhe NULL per valori che dimentichi, ovviamente.

+0

C'è qualcosa di simile in C++? – kravemir

4

Un trucco che ho fatto in passato è quello di aggiungere un enum in più e poi fare un assert momento della compilazione (come Boost's) per assicurarsi che i due sono tenuti in sincronia:

enum Fruit { 
    APPLE, 
    BANANA, 

    // MUST BE LAST ENUM 
    LAST_FRUIT 
}; 

const char *FruitNames[] = 
{ 
    "Apple", 
    "Banana", 
}; 

BOOST_STATIC_ASSERT((sizeof(FruitNames)/sizeof(*FruitNames)) == LAST_FRUIT); 

Questa volontà almeno impedisci a qualcuno di dimenticare di aggiungere sia all'enum che al nome dell'array e glielo farà sapere non appena tenteranno di compilare.

1

Non mi piacciono le soluzioni macro, in generale, anche se ammetto che è un po 'difficile da evitare.

Personalmente ho optato per una classe personalizzata in cui inserire le mie enumerazioni. L'obiettivo era offrire un po 'di più delle enumerazioni tradizionali (come l'iterazione).

Sotto la copertina, utilizzo uno std::map per mappare l'enum alla sua controparte std::string. Quindi posso usarlo per iterare su enum e "pretty print" per enum o inizializzarlo da una stringa letta in un file.

Il problema, ovviamente, è la definizione, dal momento che devo prima dichiarare l'enum e quindi mapparlo ... ma questo è il prezzo che si paga per il loro utilizzo.

Inoltre, non utilizzo un vero enum, ma un const_iterator che punta alla mappa (sotto le copertine) per rappresentare il valore enum (con end che rappresenta un valore non valido).

4

Un commento sulla soluzione macro: non è necessario un file separato per gli enumeratori.Basta usare un'altra macro:

#define FRUITs \ 
    FRUIT(Banana), \ 
    FRUIT(Apple), \ 
    FRUIT(Pear), \ 
    FRUIT(Tomato) 

(. Io probabilmente lasciare le virgole fuori, però, e incorporarli nella macro FRUTTA se necessario)

1

Date un'occhiata a biblioteca Metaresc https://github.com/alexanderchuranov/Metaresc

E ' fornisce un'interfaccia per la dichiarazione dei tipi che genererà anche meta-dati per il tipo. Basati su meta-dati, puoi facilmente serializzare/deserializzare oggetti di qualsiasi complessità. È possibile serializzare/deserializzare XML, JSON, XDR, notazione Lisp-like, notazione C-init.

Ecco un semplice esempio:

#include <stdio.h> 
#include <stdlib.h> 
#include <inttypes.h> 

#include "metaresc.h" 

TYPEDEF_ENUM (fruit_t, 
       Apple, 
       Banana, 
       Pear, 
       Tomato, 
      ); 

int main (int argc, char * argv[]) 
{ 
    mr_td_t * tdp = mr_get_td_by_name ("fruit_t"); 

    if (tdp) 
    { 
     int i; 
     for (i = 0; i < tdp->fields_size/sizeof (tdp->fields[0]); ++i) 
     printf ("[%" SCNd64 "] = %s\n", tdp->fields[i].fdp->param.enum_value, tdp->fields[i].fdp->name.str); 
    } 
    return (EXIT_SUCCESS); 
} 

Questo programma emette

$ ./enum 
[0] = Apple 
[1] = Banana 
[2] = Pear 
[3] = Tomato 

Biblioteca funziona bene per l'ultimo gcc e clang.

1

C'è anche Better Enums, che è una libreria di sola testa (file) che richiede C++ 11 ed è concessa in licenza con la licenza del software BSD. Descrizione ufficiale:

Enumerazione riflettente in fase di compilazione per C +: Better Enums è un file di intestazione singolo e leggero che consente al compilatore di generare tipi di enumerazione riflettenti.

Ecco l'esempio di codice dal sito ufficiale:

#include <enum.h> 

BETTER_ENUM(Channel, int, Red = 1, Green, Blue) 

Channel  c = Channel::_from_string("Red"); 
const char *s = c._to_string(); 

size_t  n = Channel::_size(); 
for (Channel c : Channel::_values()) { 
    run_some_function(c); 
} 

switch (c) { 
    case Channel::Red: // ... 
    case Channel::Green: // ... 
    case Channel::Blue: // ... 
} 

Channel  c = Channel::_from_integral(3); 

constexpr Channel c = 
    Channel::_from_string("Blue"); 

Sembra molto promettente, anche se non ho ancora testato. Inoltre ci sono un sacco di librerie di riflessione (personalizzate) per C++. Spero che qualcosa di simile a Better Enums faccia parte della STL (Standard Template Library) (o almeno Boost), prima o poi.

Problemi correlati