2009-11-26 8 views
16

que he encontrado esta situación tantas veces ...C/C++: ¿alguna forma de obtener enumeraciones reflectantes?

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

Ahora tengo Fruit f; // banana y quiero ir de f a la cadena "Banana"; o tengo string s = "Banana" y de eso quiero ir al Banana // enum value or int.

Hasta ahora he estado haciendo esto .. Suponiendo que la enumeración está en Fruit.h:

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

Obviamente eso es una solución desordenado. Si un desarrollador agrega una nueva fruta al encabezado y no agrega una nueva entrada en Fruits [] (no lo culpo, ¡tienen que estar en dos archivos diferentes!) La aplicación se dispara.

¿Hay una manera simple de hacer lo que quiero, donde todo está en un solo archivo? Preprocesador piratea, magia extraterrestre, cualquier cosa ...

PD: Esto, al contrario de la reflexión "para todo", sería realmente trivial de implementar en compiladores. Viendo lo común que es un problema (al menos para mí), realmente no puedo creer que no haya reflective enum Fruit. Ni siquiera en C++ 0x.

PS2: Estoy usando C++ pero etiqueté esta pregunta como C porque C tiene el mismo problema. Si su solución incluye C++ solo cosas, eso está bien para mí.

+2

Esto se ve notablemente similar a: http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c/ –

+0

@Jonathan: yes , aunque esto requiere un mapeo bidireccional. Y string-to-int es más difícil que int-to-string mappings. – MSalters

+1

@ Mark: Disculpe mi ignorancia, pero ¿qué, biológicamente, es un tomate si no es una fruta en su libro? – DevSolar

Respuesta

32

Esto requiere que las frutas se definan en un archivo externo. Este sería el contenido de 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 

Y esto sería fruta-defs.h:

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

Funciona siempre y cuando los valores comienzan en 0 y son consecutivos ...

Actualización: mezclar esta solución con la de Richard Pennington utilizando C99 si necesita no consecutivos ve valores. Es decir, algo así como:

// 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

Esa es una buena idea. –

+0

Nunca pensé en hacerlo de esa manera. – Eld

+0

Desenterrando esta publicación anterior, lo siento por eso.¿Podrías explicar qué hacen NUM_FRUITS y NULL en Fruit and Fruits * respectivamente? ¿Por qué es necesario, qué hace y dónde se declaran NUM_FRUITS? – Mads

3

¿Qué pasa si haces algo como esto?

enum Fruit { 
    Apple, 
    Banana, 
    NumFruits 
}; 

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

Entonces, si se agrega una nueva entrada a la enumeración de frutas, el compilador debe quejan de que no haya suficientes entradas en el inicializador de la matriz, por lo que se vería obligado a añadir una entrada a la matriz.

Por lo tanto, protege de que la matriz tenga el tamaño incorrecto, pero no ayuda a garantizar que las cuerdas sean correctas.

+1

Sí, pero 'const char * A [50] = {" A "};' funciona de todos modos. Por lo tanto, solo protege contra la adición de nuevos elementos a Fruit sin cambiar la matriz: si alguien elimina una fruta de la enumeración o intercambia dos valores, la matriz se desincorporará. –

+1

Creo que depende del compilador que esté utilizando y de las advertencias que haya activado. Mire las opciones de su compilador y vea si puede habilitar una advertencia para inicializadores incompletos si aún no lo hace. –

+0

No estoy seguro de ningún compilador C/C++ que genere esta advertencia ... así que podría estar totalmente equivocado. Si es así, entonces es solo una ilusión. –

1

Como han demostrado las otras personas que respondieron la pregunta, no hay realmente una manera limpia ("D.R.Y.") de hacerlo con el preprocesador C solo. El problema es que necesita definir una matriz de tamaño de su enume que contiene cadenas correspondientes a cada valor enum, y el preprocesador C no es lo suficientemente inteligente como para poder hacer eso. Lo que hago es crear un archivo de texto como este:

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

% 

Aquí% 's mark delimiters.

A continuación, un script de Perl, la parte de trabajo de que se parece a esto,

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

lee este archivo y escribe un archivo de cabecera:

typedef enum kinopiko_status { 
    kinopiko_status_ok, 
    kinopiko_status_eof_reading_content, 

y un archivo 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. ", 

usando el archivo de entrada en la parte superior. También calcula el número 26 aquí contando las líneas de entrada. (De hecho, hay veintiséis estados posibles).

Luego, la construcción del archivo de cadena de estado se automatiza utilizando make.

+0

Me encantaría saber por qué esto fue downvoted. Lo anterior se toma de un programa de trabajo (¡solo se cambia el nombre!). –

+0

Creo que su respuesta sería mucho más clara si publicara algunos de los guiones de Perl. En particular, ¿por qué hay cadenas '% status' y '% meaning'? ¿Qué tienen que ver con la pregunta? Además, el 'kinopiko_' no está claro hasta que alguien mira tu nombre de usuario. –

+0

No hay mucho que pueda hacer sobre esa última queja, ya que si publicara el nombre real, significaría aún menos para usted. He publicado un fragmento del analizador de perl para esto. –

1

podría hacer una estructura de clases para ello:

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

, entonces puede tener una función que toma una fruta, y será incluso más un tipo seguro.

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

El tamaño de esto es 2 veces más grande que solo una enumeración. Pero más que probable que eso no importe.

9

Una manera c99 que he encontrado ayuda a reducir errores:

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

Puede añadir enumeraciones, incluso en el medio, y no romper las definiciones de edad. Todavía puede obtener cadenas NULL para los valores que olvida, por supuesto.

+0

¿Hay algo similar posible en C++? – kravemir

4

Un truco que he hecho en el pasado es agregar una enumeración adicional y luego hacer una aserción tiempo de compilación (como Boost's) para asegurarse de que los dos se mantienen en sincronía:

enum Fruit { 
    APPLE, 
    BANANA, 

    // MUST BE LAST ENUM 
    LAST_FRUIT 
}; 

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

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

Esta voluntad al menos evite que alguien olvide agregar a la matriz de enumeración y nombre y les avisará tan pronto como intenten compilar.

1

No me gustan las soluciones macro, en general, aunque admito que es un poco difícil evitarlas.

Personalmente, opté por una clase personalizada para envolver mis enums. El objetivo era ofrecer un poco más que las enumeraciones tradicionales (como la iteración).

Bajo la cubierta, uso std::map para asignar la enumeración a su contraparte std::string. Entonces puedo usar esto para iterar sobre la enumeración y "imprimir bastante" mi enumeración o inicializarla desde una cadena leída en un archivo.

El problema, por supuesto, es la definición, ya que primero tengo que declarar la enumeración y luego asignarla ... pero ese es el precio que paga por usarlas.

Además, entonces no uso una enumeración real, sino un const_iterator apuntando al mapa (debajo de las cubiertas) para representar el valor enum (con end representando un valor no válido).

4

Un comentario sobre la solución de macros: no necesita un archivo separado para los enumeradores.Sólo tiene que utilizar otra macro:

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

(. Yo probablemente dejar las comas a cabo, sin embargo, e incorporarlos a la fruta macro según sea necesario)

1

Tome un vistazo a la biblioteca Metaresc https://github.com/alexanderchuranov/Metaresc

Se proporciona una interfaz para la declaración de tipos que también generará metadatos para el tipo. Según los metadatos, puede serializar/deserializar fácilmente objetos de cualquier complejidad. Fuera de la caja, puede serializar/deserializar XML, JSON, XDR, notación tipo Lisp, notación C-init.

Aquí está un ejemplo sencillo:

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

Este programa dará salida

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

Biblioteca funciona bien para la última gcc y sonido metálico.

1

También hay Better Enums, que es una biblioteca de cabecera (archivo) que requiere C++ 11 y está licenciada bajo la licencia del software BSD. Descripción oficial:

Enumeraciones reflectoras en tiempo de compilación para C +: Better Enums es un archivo de cabecera único y liviano que hace que el compilador genere tipos de enum reflectantes.

Aquí está el ejemplo de código de la página web oficial:

#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"); 

Se ve muy prometedor, aunque no lo he probado todavía. Además, hay muchas bibliotecas de reflexión (personalizadas) para C++. Espero que algo similar a Better Enums sea parte de la Standard Template Library (STL) (o al menos Boost), tarde o temprano.

Cuestiones relacionadas