2009-10-12 11 views
6

Estoy creando mi propia implementación de XUL en C++ utilizando la API de Windows. El hecho de que los elementos sean construidos por el analizador XML requiere que tengan interfaces idénticas, de modo que no es necesario escribir código personalizado para cada constructor de elementos. El resultado es que la mayoría de mis elementos se ven así:D.R.Y frente a "evitar macros"

class Button : public Element 
{ 
public: 
    static const char * Type() { return "button"; } 

private: 
    friend class Element; 
    Button(Element * inParent, const AttributesMapping & inAttributesMapping); 
}; 


class Label : public Element 
{ 
public: 
    static const char * Type() { return "label"; } 

private: 
    friend class Element; 
    Label(Element * inParent, const AttributesMapping & inAttributesMapping); 
}; 


class Description : public Element 
{ 
public: 
    static const char * Type() { return "description"; } 

    virtual bool init(); 

private: 
    friend class Element; 
    Description(Element * inParent, const AttributesMapping & inAttributesMapping); 
}; 

Aquí hay mucha duplicación de código. Me pregunto si sería una buena idea para reemplazarlos con las llamadas macro como esta:

#define DECLARE_ELEMENT(ElementType, XULName)   \ 
class ElementType : public Element      \ 
{              \ 
public:             \ 
    static const char * Type() { return XULName; }  \ 
                 \ 
private:            \ 
    friend class Element;        \ 
    ElementType(          \ 
     Element * inParent,        \ 
     const AttributesMapping & inAttributesMapping); \ 
};              \ 


DECLARE_ELEMENT(Window, "window") 
DECLARE_ELEMENT(Button, "button") 
DECLARE_ELEMENT(Label, "label") 

no he trabajado por completo el concepto todavía, así que algunas cosas que faltan aquí, al igual que las definiciones de clases, y (tal vez) la capacidad de agregar métodos por elemento.

Pero me gustaría saber su opinión sobre el uso de macros en esta situación. Siéntete libre de compartir tus pensamientos.

EDITAR

Ahora estoy usando un pequeño script Ruby que genera los archivos de origen y de cabecera de un conjunto de plantillas. Mejoré los scripts para que los archivos también se marquen automáticamente para agregarlos en SVN, y el archivo de proyecto de Visual Studio se modifique para incluir los archivos. Esto me ahorra mucho trabajo manual. Estoy bastante feliz con esta solución. FYI esto es lo que las plantillas ven ahora:

#ifndef {{ELEMENT_NAME_UPPER}}_H_INCLUDED 
#define {{ELEMENT_NAME_UPPER}}_H_INCLUDED 


#include "XULWin/Element.h" 


namespace XULWin 
{ 

    class {{ELEMENT_NAME}} : public Element 
    { 
    public: 
     static ElementPtr Create(Element * inParent, const AttributesMapping & inAttr) 
     { return Element::Create<{{ELEMENT_NAME}}>(inParent, inAttr); } 

     static const char * Type() { return "{{ELEMENT_TYPE}}"; } 

     virtual bool init(); 

    private: 
     friend class Element; 
     {{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping); 
    }; 

} // namespace XULWin 


#endif // {{ELEMENT_NAME_UPPER}}_H_INCLUDED 

CPP documento:

#include "XULWin/{{ELEMENT_NAME}}.h" 
#include "XULWin/{{ELEMENT_NAME}}Impl.h" 
#include "XULWin/AttributeController.h" 
#include "XULWin/Decorator.h" 


namespace XULWin 
{ 

    {{ELEMENT_NAME}}::{{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping) : 
     Element({{ELEMENT_NAME}}::Type(), 
       inParent, 
       new {{ELEMENT_NAME}}Impl(inParent->impl(), inAttributesMapping)) 
    { 
    } 


    bool {{ELEMENT_NAME}}::init() 
    { 
     return Element::init(); 
    } 

} // namespace XULWin 
+0

Apoyo DRY. hacer las cosas más limpias – mauris

+0

Uso macros principalmente para evitar * símbolo * repeticiones en uno o dos líneas. Un muro '# define' como el suyo a menudo se puede resolver a una función/plantilla auxiliar y, a veces, a una macro de declaración simpel. MartinB ha demostrado una buena solución para su caso, ni siquiera necesita una macro. – peterchen

+1

¿Por qué no estás haciendo la versión de la plantilla? – GManNickG

Respuesta

5

No utilizaría una macro aquí. La clave está en su clase "Descripción", que tiene una función de miembro adicional init, que los otros no. Por lo tanto, no podría usar la macro para definirla, pero en su lugar, debería expandir la macro manualmente y agregar la línea adicional.

Para mí, esta es una violación más grande de DRY que simplemente escribir todas las definiciones de clase. Casi no se repite, pero si lo hace solo por un caso, a menudo resulta más difícil mantener esa repetición constante. DRY se trata de encontrar buenas abstracciones, no solo de reducir las repeticiones.

Podría reemplazar esos constructores, sin embargo, con una función SetAttributes en la clase Element. Eso podría reducir la cantidad de repetición requerida en cada clase derivada, ya que los constructores son lo único que no se puede heredar de la base. Pero depende de cuán similares sean las implementaciones del constructor de cada clase.

4

Creo que las macros pueden estar bien para reducir la repetición (y por tanto, el riesgo de introducir errores) a un bajo nivel Me gusta esto.

El uso de macros seguirá siendo muy localizado, y debería hacer que el código en su conjunto sea más fácil de entender. Por supuesto, también podría requerir algún esfuerzo de documentación.

+0

Estoy totalmente de acuerdo. Evitar Macros no significa prohibirlos a todos. En este caso, su código se hace mucho más claro con las macros y mucho menos propenso a errores a través de DRY. – Dolphin

0

Yo votaría por las macros en este caso. No son tan malos después de todo, no deberías tratar de escribir funciones en línea con ellos, pero aparte de eso están bien.

3

Utilice lo que sea que simplifique el código.

DRY y Avoid Macro tienen el mismo objetivo: simplificar el código.

  • SECO: evitar la repetición
  • Evitar Macro: porque pueden introducir difícil de diagnosticar errores de compilación o difícil de diagnosticar errores (ya que evitan los límites de espacio de nombres y no son conscientes de C++/typesafe).

Como es habitual con las directrices, sugiero seguir el espíritu en lugar de la letra. En su caso, parece evidente que la macro realmente simplificará su código, por lo que probablemente debería usarlo.

Sin embargo, teniendo en cuenta los problemas que puede presentar una macro, asegúrese de ponerle el nombre "de forma segura". Incluya el nombre del proyecto/nombre del archivo al comienzo, por ejemplo, para reducir el potencial "conflicto" con una macro existente.

(se puede echar un vistazo a los guardias de cabecera impulso para tener una idea de la convención de nombres)

0

Creo que el uso de macros está bien en este caso, pero sólo si

  • puede desarrollar una solución que no es demasiado complejo pero cubiertas (preferiblemente) todos los Element estructuras de clase necesarias
  • documento el pozo macro
  • son conscientes de que algunos IDE tienen problemas con las estructuras de clase generados por macro y pueden vivir con la c onsequences
3

Tenga cuidado con el uso de macros que reemplazan las definiciones class si planea utilizar herramientas de documentación de código automático como doxygen. Tendrá que ejecutar el código a través del preprocesador antes de generar cualquier documentación. No, tal vez, la consideración más importante, pero algo a considerar, no obstante.

+0

¡Gracias por la pista! No había pensado tan lejos aún. – StackedCrooked

+0

Estoy trabajando en algún código heredado que usara macros para reemplazar todas las delcaraciones de clases y funciones y definiciones. Hace difícil manejar la estructura del programa, ya que confunde tanto el IDE como los generadores de documentación. –

2

En mi humilde opinión esta macro está justificada. Aunque creo que sería mejor agregar #undef DECLARE_ELEMENT para evitar macros colgantes. (A menos que planee utilizar esta macro en otros archivos también).

Sin embargo, tenga en cuenta que esto solo funcionará si esas clases nunca difieren mucho (o lo mejor de todo).


Existe otra solución que utiliza plantillas. Considere seguir el código

namespace impl 
{ 
    struct ButtonTag; 
    struct LabelTag; 


    template< typename TypeTag > 
    struct NameGenerator; 

    template<> 
    struct NameGenerator<ButtonTag> 
    { 
     static const char * getName() { return "button"; } 
    }; 

    template<> 
    struct NameGenerator<LabelTag> 
    { 
     static const char * getName() { return "label"; } 
    }; 


    template< typename TypeTag > 
    class SimpleElement : public Element 
    { 
    public: 
     static const char * Type() 
     { return NameGenerator<TagType>::getName(); } 

    private: 
     friend class Element; 

     SimpleElement(
      Element * inParent, 
      const AttributesMapping & inAttributesMapping); 

    }; 
} 

typedef impl::SimpleElement<impl::ButtonTag> Button; 
typedef impl::SimpleElement<impl::LabelTag> Label; 

Es algo más prolijo pero evita las macros.

1

ejemplos de código

enum Types { BUTTON, LABEL,...} 

struct TypeList { 
    static const char * Type(const int nID) 
    { 
     switch(nID) { 
     case BUTTON: return "button"; 
     ... 
    } 
}; 

template<ID> 
class IElem : public Element 
{ 
private: 
    static TypeList m_oTypeList; 

public: 
    static const char * Type() { return m_oTypeList.Type(ID); } 
private: 
friend class Element; 
    IElem(Element * inParent, const AttributesMapping & inAttributesMapping) 
    {...} 
}; 

para funciones que no son comunes y especializados

class Button : public IElem<BUTTON> 
{ 
... 
} 
39

Si se utiliza una solución de plantilla, puede evitar macros y evitar la repetición de sí mismo:

template <const char *XULName> 
class ElementType : public Element 
{ 
public: 
    static const char * Type() { return XULName; } 

private: 
    friend class Element; 
    ElementType(
     Element * inParent, 
     const AttributesMapping & inAttributesMapping); 
}; 

char windowStr[]="window"; 
char buttonStr[]="button"; 
char labelStr[]="label"; 

typedef ElementType<windowStr> Window; 
typedef ElementType<buttonStr> Button; 
typedef ElementType<labelStr> Label; 

Regla de oro: Las plantillas se pueden utilizar para casi todo lo que las macros eran necesarias para

en C. nota

implementación: los literales de cadena no puede ser utilizado directamente como argumentos de plantilla porque tienen vinculación interna - es por eso que necesita el windowStr etc. En la práctica, se quiere poner las declaraciones de windowStr, una buttonStr nd labelStr en el archivo H y las definiciones de esas cadenas en un archivo CPP.

+1

+1, sabía que no se podía usar una cadena literal como argumento de plantilla, pero esa sería la primera vez que veo este trabajo. No estoy seguro de que me gustaría usarlo, ¡pero siempre es bueno tener más herramientas para el trabajo! –

+0

@ Matthieu: Estoy de acuerdo ... Encuentro esta solución un tanto dudosa, también. Una solución más agradable sería usar una clase de rasgos ... lo más probable es que, en este tipo de situaciones, eso se requiera tarde o temprano de todos modos. –

5

Como alternativa, puede considerar generar el código en un paso de compilación por separado, en lugar de usar el preprocesador. Me gusta cog, pero puede usar lo que quiera: de esta manera obtiene un control programático completo sobre lo que se genera. (Las macros son potentes, pero están limitadas en lo que se puede hacer).

+0

Me sorprendió que nadie más viera la tercera opción. ++ para la generación de código. Puede agregar partes personalizadas donde las necesite. Puede generar la documentación adecuada. No se divide la numeración/depuración de línea de la forma en que macro lo haría. Si no puede usar una plantilla de C++ para esta solución, use la generación de código. – viraptor

1

Incluso podría ir un poco más lejos y usar las funciones hash único y hash doble al usar macros. Hash único crea constantes de cadena y los identificadores de concatenación doble para crear nuevos combinados.

#define DECLARE_ELEMENT(ElementType)      \ 
class C## ElementType : public Element     \ 
{              \ 
public:             \ 
    static const char * Type() { return # ElementType; } \ 
                 \ 
private:             \ 
    friend class Element;        \ 
    C## ElementType(         \ 
     Element * inParent,        \ 
     const AttributesMapping & inAttributesMapping); \ 
} 

DECLARE_ELEMENT(window); // defines Cwindow 
DECLARE_ELEMENT(button); // defines Cbutton 
DECLARE_ELEMENT(label); // defines Clabel 

Por ejemplo, el código siguiente es algo que a veces escribo para probar el tamaño de algunos tipos comunes.

#include <stdio.h> 

#define OUT(_type) printf("sizeof(%s) = %d\n", #_type, sizeof(_type)) 

int main() { 
    OUT(char); 
    OUT(int); 
    OUT(short); 
    OUT(long); 
    OUT(long long); 
    OUT(void*); 
    return 0; 
} 
0

Este código es un montón, como el programa que Tom Cargill disecciona y vuelve a montar en el capítulo 1 de su libro "C++ Programming Style", que data de 1992. Es cierto que el código no utilizar macros para replicar el casi idéntico clases, pero el resultado neto se ve muy similar desde aquí.

+0

¿Me estás engañando para que revise este libro?:) – StackedCrooked

+0

@StackedCrooked: de la biblioteca - sí; comprando, no necesariamente. Hay libros más recientes que cubren mejor el C++ moderno, pero esto todavía cubre gran parte de los conceptos básicos y no está, en mi opinión, completamente desactualizado. Todavía puedes comprarlo nuevo en Amazon, lo que me sorprendió gratamente (le quedaba uno cuando lo busqué). –

+0

La esencia del capítulo es que no crea clases que solo difieren en los valores devueltos. Su macroesquema no apropiadamente (o, tal vez de manera más caritativa o precisa, lo suficiente) permite la variación en las clases derivadas. En términos generales, si puede tratar todo en macros, no hay suficiente diferencia entre las clases para garantizar el uso de diferentes clases, sino que deben manejarse mediante la parametrización (por valor) de una clase más genérica. Es un resumen muy rápido en quizás 20 páginas, aunque el material de cada página no es tan denso (es un libro abierto y fácil de leer). –