2011-04-01 18 views
5

Considere una clase C++ que exporta una enumeración, mantiene una matriz interna sobre esa enumeración, y desea exportar un comando que acepte valores de la enumeración.Controlando la visibilidad de los valores enum

class foo { 
public: 
    enum color { 
    red, 
    yellow, 
    green, 
    NUM_COLORS 
    }; 
private: 
    something somebody[NUM_COLORS]; 
public: 
    void command(color c); 
}; 

¿Hay una manera limpia de exportar solo los colores reales, pero no NUM_COLORS? No quiero tener que buscar el caso límite en cada llamada cuando el sistema de tipo de compilación realmente debería poder hacerlo por mí.

El truco es obvia:

class foo { 
public: 
    enum color { 
    red, 
    yellow, 
    green 
    }; 
private: 
    /* something like */ const unsigned NUM_COLORS = green+1; 
    unsigned LEDs_in_stock[NUM_COLORS]; 
public: 
    void command(color c); 
}; 

Por supuesto, esto es una bomba de tiempo, a la espera de algún pobre programador de mantenimiento con exceso de trabajo para agregar disposiciones para los LEDs azules, y se olvidan de actualizar la línea NUM_COLORS.

Déjenme aclarar un poco. Lo que yo quiero, en este caso particular, es ser capaz de decir:

class foo { 
public: 
    enum color { 
    red, 
    yellow, 
    green 
    }; 
    void command(color c); 
private: 
    something somebody[color]; 
}; 

Es mi entendimiento de que C++ no permite esto.

+1

Hasta donde yo sé, lo que estás tratando de hacer no es realmente posible. Lo siguiente que viene a la mente es mantener presionada la tecla Mayús y escribir un COMENTARIO MUY IMPORTANTE debajo de la enumeración. –

+0

El compilador no lo ayudará desde el estúpido. Alguien podría escribir fácilmente foo :: color (7) de todos modos. –

Respuesta

2

Mi primer pensamiento sería para tratar de resolver el problema, ya que la pongo, pero después de una reflexión poco, sería trasladar la carga a command:

void command(color c) { 
    assert(0 <= c && c < NUM_COLORS && "Invalid argument"); 
} 

Desde enumeraciones son tipos tan débiles , es necesario comprobar la entrada de todos modos, ya que cualquiera podría fácilmente dar argumentos cutres:

Foo foo; 
foo.command(static_cast<Foo::color>(3)); // 3 is green, right ? 

solución original:

class Foo { 
    struct impl { enum { red, yellow, green, NUM_COLORS }; }; 
public: 
    enum color { red = impl::red, yellow = impl::yellow, green = impl::green }; 

    void command(color c); 
}; 

Desafortunadamente, hay un alto grado de duplicación pasando (y de hecho originalmente escrito green = impl::yellow; aunque no importa si nunca se refieren a impl 's valores directamente).

De lo contrario, siempre existe el truco macro:

#define MY_DEFINE_ENUM(Type, Elements)  \ 
    enum Type { BOOST_PP_SEQ_ENUM(Elements) }; \ 
    inline size_t size(Type) { return BOOST_PP_SEQ_SIZE(Elements); } 

que utiliza un preprocesador macro maquinaria y oscuro para evitar el mal código duplicaton. Obviamente, solo funciona para elementos enum consecutivos (devuelve el número de elementos, no el número máximo).

+0

parece que está lo más cerca que voy a llegar a lo que quiero en C++. También me muestra cómo resolver la generalización (que esperaba ser dolorosa) de querer exportar algunos pero no todos, no necesariamente literales enum contiguos. Parece que hay algunas cosas que el comité de diseño de C++ simplemente no comprende. –

+0

@John R. Strohm: Estoy de acuerdo, el 'enum' en C++ está algo roto. Hay dos conceptos integrados en uno: la capacidad de simplemente especificar una enumeración y la capacidad de "asignar" valores a los nombres. Son semánticamente diferentes y arrojarlos en un solo concepto lo hacen incómodo (eso y la falta de introspección, no veo por qué no ofrecen introspección en tiempo de compilación ya que es gratis). –

+0

No sé lo suficiente sobre introspección, tiempo de compilación o de otro tipo, para comentar directamente. Observo que la característica que deseo ha estado en Ada desde la primera versión del lenguaje, lanzada oficialmente en 1983. –

0

Una solución sería utilizar un mapa:

std::map<color, unsigned> LEDs_in_stock; 

LEDs_in_stock[red] += 2; 

LEDs_in_stock[red]; // = 2 
LEDs_in_stock[green]; // = 0 

De esta manera se mantiene limpia que ENUM, y no es necesario codificar cualquier tamaño.

+0

Eché un vistazo al archivo del encabezado del mapa, y parece a primera vista llevar mucho equipaje. Necesito armar un programa de prueba y ver qué tipo de código realmente se genera. No puedo vivir con una solución que impone cientos o miles de instrucciones de sobrecarga para una simple referencia de matriz, y no debería tener que hacerlo. –

+0

Teniendo en cuenta la sobrecarga del nodo del mapa con respecto a su baja carga útil, parece demasiado exagerado. –

+0

Por supuesto es más pesado, pero creo que el código C++ es más importante que el código binario. Es mejor tener un código C++ muy simple, seguro y funcional que genere un binario más grande, ya que no será difícil para el procesador eximirlo. por lo tanto, es un problema para el programador mantener un código hacky. Esa es la solución que elegiría personalmente, pero depende de usted, por supuesto ^^ –

3

¿Está poniendo la enumeración en una clase base una opción?

class foo_enums { 
public: 
    enum color { 
    red, 
    yellow, 
    green, 
    NUM_COLORS 
    }; 

protected: 
    foo_enums() { } 
    ~foo_enums() { } 
}; 

class foo : public foo_enums { 
private: 
    unsigned LEDs_in_stock[NUM_COLORS]; 

    /* make NUM_* values inaccessible */ 
    using foo_enums::NUM_COLORS; 

public: 
    void command(color c); 
}; 

Personalmente, no haría esto, ya que parece una tarea demasiado compleja. Simplemente prohibiría que la persona que llama pase NUM_COLORS. Es cierto que el sistema de tipo no verifica eso. Pero seguramente esto es algo fácil de verificar para los programadores humanos. ¿Por qué pasarían NUM_COLORS?

+0

Siguiente pregunta: ¿cómo se esconde 'foo_enums'? Cualquiera puede usar 'foo_enums :: NUM_COLORS' ... –

+0

@ André: Ponlo en un espacio de nombres' detail' y cada programador en serio sabe que ni siquiera deberían pensar en tocarlo. – Xeo

+1

@Andre cualquiera puede desreferenciar un puntero nulo. ¿Cómo les impedimos hacer eso? Si no pueden leer y seguir un comentario diciendo "Esta constante enum solo debe ser usada por la clase" foo ", entonces es una pena. Citando a Herb Sutter: * Recuerda distinguir entre" proteger contra Murphy versus proteger contra Maquiavelo ". * –

1

Hay una línea muy fina entre proteger a sus futuros mantenedores de cometer errores simples/fáciles e intentar detener algo que debería ser obviamente incorrecto, como usar el valor de NUM_COLORS.

En su caso, le sugiero que afirme la entrada en las funciones clave y lo deje así.

Creo que podría utilizar una clase de proxy de plantilla que se especializa y static_assert s en NUM_COLORS para evitar que los usuarios lo pasen a sus funciones.

Escribí algo que parece funcionar.

class foo { 
public: 
    enum color { 
    red, 
    yellow, 
    green, 
    NUM_COLORS 
    }; 

    class Color_Rep 
    { 
    color c; 
    protected: 
    Color_Rep(color which_color) : c(which_color) { } 
    }; 

    template <color C> 
    struct Color : public Color_Rep 
    { 
    Color() : Color_Rep(C) { } 
    enum { value = C }; 
    }; 

private: 
    int bar[NUM_COLORS]; 

public: 
    void command(Color_Rep c); 
}; 

// Deny access to command(NUM_COLORS). 
template <> 
struct foo::Color<foo::NUM_COLORS> 
{ 
}; 

int main() 
{ 
    foo().command(foo::Color<foo::red>()); 
    foo().command(foo::Color<foo::green>()); 
    foo().command(foo::Color<foo::NUM_COLORS>()); // Won't compile. 
} 
+0

si hay algo que he aprendido, es que se espera que las personas no hagan algo que sea * Obviamente * incorrecto es un ejercicio de temeridad. Lo harán, con la garantía, a menos que PREVIENE. Ejemplo: El grupo de software de Nortel Networks tenía una política de excepciones sin excepciones escrita y rápida "Deberá probar el nulo antes de desreferenciar un puntero. "¿Adivina qué hizo un codificador sin nombre? (Adivina quién llegó a rastrear el crash duro intermitente resultante?) –

+0

@John R.Strohm personalmente considero que el ejemplo de Nortel es completamente diferente de esta situación. Es * fácil * olvidar comprobar un puntero para nulo (no discutiré si es una buena idea o no) pero en mi mente usar un valor enumerado 'NUM_COLORS' es totalmente diferente: una decisión explícita del codificador para usarlo . ¿Pasamos por aros para tratar de evitar que las personas de '#define private public'? –

Cuestiones relacionadas