2009-10-12 5 views
6

Sto creando la mia implementazione di XUL in C++ utilizzando l'API di Windows. Il fatto che gli elementi siano costruiti dal parser XML richiede che abbiano interfacce identiche, quindi non è necessario scrivere codice personalizzato per ogni costruttore di elementi. Il risultato è che la maggior parte dei miei elementi assomiglia a questo:D.R.Y vs "avoid 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); 
}; 

Quindi c'è molta duplicazione di codice qui. Mi chiedo se sarebbe una buona idea per sostituirli con le chiamate di macro come questo:

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

non ho completamente elaborati il ​​concetto ancora, quindi alcune cose sono mancanti qui, come le definizioni di classe, e (forse) la possibilità di aggiungere metodi per elemento.

Ma mi piacerebbe sapere la tua opinione sull'utilizzo di macro in questa situazione. Sentiti libero di condividere i tuoi pensieri.

EDIT

ora sto usando un piccolo script Ruby che genera i file di origine e colpo di testa da una serie di modelli. Ho migliorato gli script in modo che anche i file vengano automaticamente contrassegnati per l'aggiunta su SVN e il file di progetto di Visual Studio viene modificato per includere i file. Questo mi fa risparmiare un sacco di lavoro manuale. Sono abbastanza contento di questa soluzione. FYI questo è ciò che i modelli sembrano ora: documento

#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:

#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

Supporto SOSTANZA. rendere le cose più pulite. – mauris

+0

Uso principalmente le macro per evitare la ripetizione * di simboli * in una o due righe. Un muro '# define' come il tuo può spesso essere risolto con una funzione/template helper e - a volte - una macro di dichiarazione semplificata. MartinB ha dimostrato una buona soluzione per il tuo caso, senza nemmeno bisogno di una macro. – peterchen

+1

Allora, perché non stai semplicemente facendo la versione del modello? – GManNickG

risposta

5

Non vorrei usare una macro qui. L'indizio è nella classe "Descrizione", che ha una funzione membro aggiuntiva init, che gli altri non fanno. Quindi non si sarebbe in grado di utilizzare la macro per definirla, ma si dovrebbe invece espandere la macro manualmente e aggiungere la linea aggiuntiva.

Per me, si tratta di una violazione più grave di ASCIUTO che la semplice scrittura di tutte le definizioni di classe. Quasi il non ripetersi, ma farlo solo per un caso, spesso finisce più difficile per sostenere che ripetersi in modo coerente. ASCIUTTO significa trovare buone astrazioni, non solo ridurre la caldaia.

Potrei sostituire quei costruttori, tuttavia, con una funzione SetAttributes nella classe Element. Ciò potrebbe ridurre la quantità di piastra di riscaldamento effettivamente necessaria in ogni classe derivata, poiché i costruttori sono l'unica cosa che non può essere ereditata dalla base. Ma dipende quanto siano simili le implementazioni del costruttore di ogni classe.

4

Credo che le macro possono essere giusti per ridurre la ripetizione (e, quindi, il rischio di introdurre errori) ad un livello basso come questo.

L'uso di macro rimarrà molto localizzato e dovrebbe rendere il codice nel suo complesso più facile da capire. Ovviamente potrebbe richiedere anche uno sforzo di documentazione.

+0

Sono completamente d'accordo. Evitare i macro non significa vietarli tutti insieme. In questo caso, il codice è reso molto più chiaro con i macro e molto meno incline agli errori tramite DRY. – Dolphin

0

In questo caso, voterei per i macro. Non sono poi così male, non dovresti provare a scrivere funzioni inline con loro, ma a parte questo sono buone.

3

L'uso di qualsiasi cosa semplifica il codice.

DRY ed Avoid Macro hanno lo stesso obiettivo: semplificare il codice.

  • SECCO: evitare ripetizioni
  • Evitare Macro: perché possono introdurre difficile da diagnosticare errori di compilazione o difficile da diagnosticare bug (come bypassare i confini dello spazio dei nomi e non sono a conoscenza C++/typesafe).

Come al solito con le linee guida, suggerirei quindi di seguire lo spirito piuttosto che la lettera. Nel tuo caso sembra evidente che la macro semplificherà davvero il tuo codice, quindi dovresti probabilmente usarlo.

Tuttavia, tenendo conto dei problemi che una macro può introdurre, accertarsi di denominarlo "in sicurezza". Includi all'inizio il nome del progetto/nome del file per ridurre il potenziale "scontro" con una macro esistente.

(si può dare un'occhiata a guardie di intestazione spinta per avere un'idea della convenzione di denominazione)

0

Credo che l'utilizzo di macro va bene, in questo caso, ma solo se si

  • in grado di sviluppare una soluzione che non è troppo complesso ma coperchi (preferibilmente) tutti i necessari Element strutture di classe
  • documento macro ben
  • sono consapevoli che alcuni IDE hanno problemi con macro strutture generata classe e possono vivere con la c onsequences
3

Diffidare di utilizzare macro che sostituiscono le definizioni class se si prevede di utilizzare strumenti di documentazione automatica del codice come doxygen. Dovrai eseguire il codice attraverso il preprocessore prima di generare qualsiasi documentazione. Non, forse, la considerazione più importante, ma comunque qualcosa da considerare.

+0

Grazie per il suggerimento! Non avevo ancora pensato fino ad ora. – StackedCrooked

+0

Sto lavorando su un codice legacy che utilizzava macro per sostituire tutte le delcarazioni e le definizioni di classe e funzione. Rende difficile gestire la struttura del programma in quanto confonde sia l'IDE che i generatori di documentazione. –

2

IMHO questa macro è giustificata. Anche se penso che sarebbe meglio aggiungere #undef DECLARE_ELEMENT per evitare le macro penzolanti. (A meno che non si preveda di utilizzare questa macro anche in altri file.)

Si noti tuttavia che ciò funzionerà solo se tali classi non saranno mai molto diverse (o comunque migliori).


C'è ancora un'altra soluzione con i modelli. Considera il seguente codice

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; 

È un po 'più dettagliato, ma evita le macro.

1

esempi di codice

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) 
    {...} 
}; 

per le funzioni non comuni e specializzati

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

Se si utilizza una soluzione di modello, è possibile evitare le macro e evitare di ripetere se stessi:

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; 

Regola generale: I modelli possono essere utilizzati per quasi tutto ciò che le macro erano necessarie per in C.

attuazione nota: le stringhe non possono essere utilizzati direttamente come argomenti di template, perché hanno il collegamento interno - è per questo che è necessario il windowStr ecc In pratica, si vorrebbe mettere le dichiarazioni di windowStr, buttonStr un nd labelStr nel file H e le definizioni di quelle stringhe in un file CPP.

+1

+1, sapevo che non si poteva usare una stringa letterale come argomento modello, ma sarebbe la prima volta che vedo questo lavoro. Non sono sicuro che mi piacerebbe usarlo, ma è sempre bene avere più strumenti per il lavoro! –

+0

@Matthieu: concordato ... Trovo questa soluzione anche un po 'incerta. Una soluzione più gradevole sarebbe usare una classe di tratti ... è probabile che, in questo tipo di situazione, ciò sia richiesto prima o poi comunque. –

5

In alternativa, è possibile considerare di generare il codice come una fase di compilazione separata anziché utilizzare il preprocessore. Mi piace lo cog, ma puoi usare quello che vuoi - In questo modo ottieni il pieno controllo programmatico su ciò che viene generato. (I macro sono potenti, ma limitati in quello che puoi fare.)

+0

Sono rimasto sorpreso che nessun altro abbia visto la terza opzione. ++ per la generazione del codice. Puoi aggiungere parti personalizzate dove ti servono. È possibile generare una documentazione adeguata. Non si rompe la numerazione delle righe/il debug nel modo in cui la macro lo farebbe. Se non è possibile utilizzare un modello C++ per questa soluzione, utilizzare la generazione del codice. – viraptor

1

Potrei anche andare un po 'oltre e usare sia il singolo hash che il doppio dell'hash quando usi i macro. Un singolo hash crea costanti di stringa e il doppio identificatore concatenato per creare nuove combinazioni.

#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 

Ad esempio il codice seguente è qualcosa che a volte scrivo per testare la dimensione di alcuni tipi comuni.

#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

Questo codice sembra un sacco come il programma che Tom Cargill seziona e ricompone nel capitolo 1 del suo libro "C++ Programming Style", risalente al 1992. Certo, che il codice non utilizzare le macro per replicare la quasi identica classi, ma il risultato netto sembra terribilmente simile da qui.

+0

Mi stai insinuando nel dare un'occhiata a questo libro?:) – StackedCrooked

+0

@StackedCrooked: dalla libreria - sì; comprare - non necessariamente. Ci sono libri più recenti che trattano meglio il moderno C++, ma questo copre ancora molte delle basi e non è, a mio avviso, completamente obsoleto. Puoi ancora comprarlo nuovo da Amazon - cosa che mi ha piacevolmente sorpreso (ne avevo uno a sinistra quando ho guardato). –

+0

L'essenza del capitolo è che non si creano classi che differiscono solo nei valori restituiti. Lo schema macro non correttamente (o, forse più caritatevolmente o accuratamente, sufficientemente) consente la variazione nelle classi derivate. In parole povere, se si riesce a gestire tutto in macro, non c'è abbastanza differenza tra le classi da giustificare l'uso di classi diverse - esse dovrebbero essere gestite mediante la parametrizzazione (in base al valore) di una classe più generica. Questa è una glossa molto veloce su circa 20 pagine, anche se il materiale su ogni pagina non è così denso (è un libro aperto e di facile lettura). –

Problemi correlati