2012-05-17 29 views
13

Así que tengo este gran árbol que es básicamente un gran interruptor/caja con teclas de cadena y diferentes llamadas de función en un objeto común dependiendo de la clave y una pieza de metadatos.Metaprogramación C/C++ utilizando el preprocesador

Cada entrada básicamente se parece a esto

} else if (strcmp(key, "key_string") == 0) { 
    ((class_name*)object)->do_something(); 
} else if (... 

donde do_something puede tener diferentes invocaciones, por lo que no se puede simplemente utilizar los punteros de función. Además, algunas claves requieren que el objeto sea lanzado a una subclase.

Ahora, si tuviera que codificar esto en un lenguaje de nivel superior, usaría un diccionario de lambdas para simplificar esto.

Se me ocurrió que podía usar macros para simplificar esto a algo así como

case_call("key_string", class_name, do_something()); 
case_call(/* ... */) 

donde case_call habría una macro que ampliaría este código para el primer fragmento de código.

Sin embargo, estoy muy en la cerca si eso se consideraría un buen estilo. Quiero decir, reduciría el trabajo de tipeo y mejoraría el DRYness del código, pero realmente parece abusar un poco del sistema macro.

¿Seguirías por ese camino o escribirías todo? ¿Y cuál sería tu razonamiento para hacerlo?

Editar

Algunas aclaraciones:

Este código se utiliza como una capa de pegamento entre un API scripting simplificado que accede a varios aspectos diferentes de un C++ API propiedades como simples clave-valor. Sin embargo, las propiedades se implementan de diferentes maneras en C++: algunas tienen métodos getter/setter, algunas se establecen en una estructura especial. Las acciones de scripts hacen referencia a objetos de C++ convertidos en una clase base común. Sin embargo, algunas acciones solo están disponibles en ciertas subclases y deben ser eliminadas.

Más adelante en el camino, puedo cambiar la API de C++ real, pero por el momento, debe considerarse como inalterable. Además, esto tiene que funcionar en un compilador integrado, por lo que boost o C++ 11 (lamentablemente) no están disponibles.

+5

¿Por qué los votos para cerrar? Esta es una muy buena pregunta válida. ¿Que me estoy perdiendo aqui? –

+3

Es una buena pregunta, pero "Esta pregunta no encaja bien en nuestro formato de preguntas y respuestas. Esperamos que las respuestas generalmente incluyan hechos, referencias o experiencia específica; * esta pregunta probablemente solicitará opinión, debate, argumentos, encuestas o ampliaciones discusión. * " – Fanael

+0

Ya está en el puntaje de +5, por lo que un poco dice que otros, yo incluido, están interesados ​​en el resultado. La respuesta a continuación ya es bastante agradable. –

Respuesta

6

Le sugiero que invierta un poco las funciones. Usted está diciendo que el objeto ya es una clase que sabe cómo manejar una situación determinada, así que agregue un virtual void handle(const char * key) en su clase base y deje que el objeto verifique la implementación si corresponde y haga lo que sea necesario.

Esto no solo eliminaría la larga cadena if-else-if, sino que también sería más segura y le daría más flexibilidad en el manejo de esos eventos.

+0

Esto no elimina la larga cadena if-else-if, simplemente la mueve/la reorganiza. Sin embargo, esta sigue siendo una muy buena idea. –

+1

@MooingDuck No eliminaría los ifs individuales, pero eliminaría la lista larga y central de casos que deben manejarse. Cada comprobación sería en la clase donde ocurre el manejo real, por lo tanto, los lugares que tienen que cambiarse para agregar nuevas funcionalidades estarían muy juntos. – Fozi

+0

Esa es una idea interesante. Me requeriría modificar las clases de implementación, que por diversas razones no es deseable en este momento, pero esta es ciertamente una buena idea. – bastibe

6

Eso me parece un uso apropiado de las macros. Después de todo, están hechos para eludir la repetición sintáctica. Sin embargo, cuando tiene repetición sintáctica, no siempre es culpa del lenguaje; probablemente haya mejores opciones de diseño que le permitan evitar esta decisión por completo.

La sabiduría general es utilizar un claves de mapeo tabla de acciones:

std::map<std::string, void(Class::*)()> table; 

A continuación, busque e invocar la acción de una sola vez:

object->*table[key](); 

O utilice find para comprobar si hay fracaso:

const auto i = table.find(key); 
if (i != table.end()) 
    object->*(i->second)(); 
else 
    throw std::runtime_error(...); 

Pero si, como dices, no hay una firma común para las funciones (es decir,, no puede utilizar los punteros de función de miembro) entonces lo que realmente debería hacer depende de las particularidades de su proyecto, que no sé. Puede ser que una macro sea la única forma de eludir la repetición que está viendo, o podría ser que hay una mejor manera de hacerlo.

Pregúntese: ¿por qué mis funciones toman argumentos diferentes? ¿Por qué estoy usando moldes? Si está distribuyendo el tipo de objeto, es probable que necesite introducir una interfaz común.

+0

"donde do_ algo puede tener invocaciones diferentes, por lo que no puedo usar indicadores de función". <- que parece que no todas las funciones van a ser 'void (Class :: *)()'. Algunos pueden requerir argumentos. Al menos eso es lo que entendí. – mfontanini

+0

@fontanini: Ah, lo perdí. Editaré –

+0

En realidad, tengo dos tipos distintos de invocaciones, así que esto realmente funcionaría si utilizara dos mapas. – bastibe