Usa macro per definire le classi che si adattano a un modello in C ++?

5

Ho un set di classi che ereditano tutte da una classe base responsabile di diverse funzioni (una specie di gruppo di "operatori"). Funzionano tutte sullo stesso input e producono lo stesso tipo di output, solo diverse operazioni sono eseguite internamente con qualche altro stato. Devono essere serializzati su JSON e resi effettivi da un'interfaccia utente.

I miei requisiti sono che le persone debbano essere in grado di lavorare su questo senza faticare a capirlo. Speravo che qualcun altro l'avesse utilizzato in un progetto diverso con altre persone e potrebbe dirmi se questa è una cattiva idea per esperienza e perché.

Per fare ciò ho utilizzato mappe che associano il nome della classe al costruttore e il metodo per serializzare la classe. All'inizio stavo inserendo manualmente le informazioni di classe in un file separato che conteneva queste mappe, poi ho trovato un metodo da fare quindi utilizzando oggetti di classe statici , dove si aggiungono automaticamente alla mappa statica delle classi astratte padre. Perché questo era necessario per ogni classe, non importa cosa, e aveva lo stesso formato esatto tranne il nome della classe utilizzata, ho creato due macro per aiutare con questo (quindi avresti bisogno solo di fare MACRO_TAG_CLASS(classname) dentro la dichiarazione della classe e MACRO_REGISTER_CLASS(classname) dopo la dichiarazione).

Ora sono arrivato a rifattorizzare il modo in cui stavo facendo la serializzazione e la visualizzazione GUI, consentendo un minor lavoro per le persone che creano nuove classi di questo tipo e rendendole più versatili per come ora voglio usarle. Alla fine ho dovuto usare le proprietà QT allo stesso modo con ogni classe, quindi ho deciso di fare una macro per gestire anche questo (Nota che sto usando il pre-processore boost per dare una mano)

Ora ho classi che sembrano tutte così:

class MyClass :
        public AbstractBase {
Q_OBJECT
private:
    TypeA m_foo;
    TypeB m_bar;
public:
    MACRO_TAG_CLASS(MyClass)

MACRO_ADD_PROPERTIES((TypeA, foo), (TypeB, bar))
//adds qt properties, signals, and adds the strings of the properties to a list for the class

   //other class functions...
   //constructor that is different per class

   //function every class in this heirarchy has, sort of operator(), 
   //but class is doesn't correspond 1:1 with the concept of an operator, so not a functor.
    AbstractBase *baz(X *x) override;

    virtual ~MyClass() = default;
};
MACRO_REGISTER_CLASS(MyClass)

e stavo pensando di cambiarli in qualcosa di simile a questo:

#define START_CLASS_OF_BASE(CLASS_NAME, ...)\
class CLASS_NAME : public AbstractBase { \
Q_OBJECT \
 public: \
    MACRO_TAG_CLASS(CLASS_NAME) \
MACRO_ADD_PROPERTIES(__VA_ARGS__) \
private:

#define END_CLASS_OF_BASE(CLASS_NAME) \
}; \
    MACRO_REGISTER_CLASS(CLASS_NAME)

START_CLASS_OF_BASE(MyClass, (TypeA, foo), (TypeB, bar))
private:
    TypeA m_foo;
    TypeB m_bar;
public:
   //other class functions...
   //constructor that is different per class

    AbstractBase *baz(X *x) override;

    virtual ~MyClass() = default;
END_CLASS_OF_BASE(MyClass)

Nota che questo modello appare solo in questa particolare gerarchia di classi, altre persone dovranno mantenere questo codice alla fine, e ci sono circa 20 classi che si adattano a questo modello.

    
posta opa 13.06.2018 - 21:42
fonte

4 risposte

8

Mi oppongo a utilizzare le macro invece della classe normale qui:

  1. È più difficile eseguire il debug, soprattutto quando ha errore di compilazione

  2. Se il costo di creare una nuova classe facilmente è più difficile da mantenere in un secondo momento, preferisco fare più lavori per creare una classe, ma più facile da mantenere più tardi

  3. Credo che in questo caso venga applicato un gold of quote: "Il codice di lettura di solito costa più tempo rispetto alla scrittura del codice"

risposta data 14.06.2018 - 05:27
fonte
11

Attualmente lavoro su un codice base che ha classi create con questi tipi di macro. Sconsiglio vivamente di fare le cose in questo modo perché se qualcosa va storto in futuro con uno qualsiasi dei codici macroizzati è impossibile eseguire il debug. È estremamente difficile cambiare la classe in qualsiasi modo. E probabilmente i macro verranno espansi manualmente in futuro, in quanto troverai casi unici e edge in cui una particolare classe creata in questo modo ha bisogno di "una cosa sola". Quindi le future modifiche alla macro non influiscono su quella classe, a meno che non ti ricordi di aggiornare anche quella copia espansa. Se esiste un modo per utilizzare invece i modelli C ++ per questo, sarebbe preferibile. (O come suggerito dai commentatori, usa una libreria che sa come farlo).

    
risposta data 14.06.2018 - 04:42
fonte
7

L'utilizzo di macro per questo diventerà un horror di manutenzione, come altri hanno già sottolineato. Come approccio alternativo, potresti implementare un piccolo generatore di codice per la creazione del codice di codice ripetuto.

Ovviamente, un generatore di codice deve essere mantenuto così come la macro, ma il vantaggio principale è che produce codice (si spera) leggibile che sarà compilato da solo, può essere debugato da solo (senza la macro che interferisce codice), ed esteso individualmente, se necessario.

Per tali generatori di codice, sono possibili due approcci: una semplice generazione iniziale monouso e quindi la manutenzione manuale del codice generato in un secondo momento, o il supporto per la rigenerazione (quindi bisogna preoccuparsi della separazione del codice generato e scritto manualmente ). Entrambi gli approcci possono essere adatti al tuo caso, devi decidere cosa si adatta meglio.

Questo di solito si ripaga quando il numero di classi crescerà in futuro, il punto di pareggio può essere compreso tra 5 e 50 classi, a seconda della complessità dei requisiti e della frequenza con cui è possibile utilizzare il generatore.

    
risposta data 15.06.2018 - 11:26
fonte
1
class MyClass :
        public AbstractBase {
Q_OBJECT

Non stai scrivendo il codice C ++ qui, stai scrivendo il codice Qt. Fondamentalmente, questo è il codice che viene prima elaborato dal compilatore Qt moc . Si aspetta di vedere Q_OBJECT macro in una classe e genererà i metadati per quella classe.

Quindi esaminiamo il tuo approccio macro:

class CLASS_NAME : public AbstractBase { \
Q_OBJECT \

Anche se moc individuerebbe Q_OBJECT , genererebbe metadati per CLASS_NAME . Ovviamente non è il nome della classe, è un parametro macro. Il problema è che moc non può capire tutte le istanze di macro e quindi non può generare i metadati.

Il problema fondamentale qui è che stai lavorando con un modello di compilazione a fasi, con un preprocessore Qt e un preprocessore C. Una delle realizzazioni del C ++ era che il preprocessore C era scomodo dal punto di vista dell'ingegneria del software. Ecco perché i modelli C ++ sono parte integrante del linguaggio.

Questo non ti aiuta con Qt o i tuoi hack del preprocessore, però. Devi convivere con il fatto che moc viene eseguito per primo.

    
risposta data 18.06.2018 - 14:26
fonte

Leggi altre domande sui tag