J'ai un ensemble de classes qui héritent d'une classe de base responsable de différentes fonctions (en quelque sorte comme un groupe d'opérateurs "), ils fonctionnent tous sur la même entrée et génèrent le même type de sortie, juste des opérations différentes sont des opérations différentes. effectué en interne avec un autre État. Ils doivent être sérialisés à JSON et factory à partir d'une interface utilisateur.
Mes exigences sont que les personnes ont besoin de pouvoir travailler à ce sujet sans avoir du mal à le comprendre. J'espérais que quelqu'un d'autre l'avait employé dans un projet différent avec d'autres personnes et que je pourrais Dis-moi s'il s'agit ou non d'une mauvaise idée de l'expérience et de la raison.
Pour ce faire, j'ai utilisé des cartes qui associent le nom de la classe au constructeur et à la méthode pour sérialiser la classe. Au début, je saisis manuellement des informations de classe dans un fichier séparé qui abritait ces cartes, puis j'ai trouvé ne méthode pour utiliser des objets de classe statique , où ils s'ajoutent automatiquement à la carte statique des classes parent. Parce que cela était nécessaire pour chaque classe, quoi que ce soit, et avait le même format exact, à l'exception du nom de la classe utilisée, j'ai créé deux macros pour aider à cela (vous n'auriez donc besoin que de MACRO_TAG_CLASS(classname)
Déclaration et MACRO_REGISTER_CLASS(classname)
Après la déclaration).
Maintenant, je suis venu à refacteur de la manière dont je faisais la sérialisation et l'affichage de l'interface graphique, permettant moins de travailler pour les personnes créant de nouvelles classes de ce type et de les rendre plus polyvalents pour la façon dont je veux maintenant les utiliser. J'ai fini par avoir besoin d'utiliser des propriétés QT de la même manière avec chaque classe, j'ai donc décidé de faire une macro à gérer cela également (note que j'utilise Boost Pre-processeur pour aider)
Maintenant, j'ai des cours qui ressemblent à ceci:
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)
et je pensais à les changer à quelque chose qui ressemble à ceci:
#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)
Notez que ce modèle n'apparaît que dans cette hiérarchie de classe particulière, d'autres personnes devront maintenir ce code éventuellement et il y a environ 20 classes qui correspondent à ce modèle.
Je m'oppose à utiliser des macros au lieu de classe normale ici parce que:
Il est plus difficile de déboguer, surtout quand il a compilé une erreur
Si le coût de la création d'une nouvelle classe est facilement plus difficile à maintenir plus tard, je préfère faire plus d'œuvres pour créer une classe mais plus facile à conserver plus tard plus tard.
Je crois qu'un or de devis serait appliqué dans ce cas: "Le code de lecture coûte généralement plus de temps que de la rédaction de code"
Je travaille actuellement sur une base de code qui dispose de classes créées avec ces types de macros. Je décourage vivement de faire des choses de cette façon, car si quelque chose ne va pas dans l'avenir avec l'un des codes macro-ized, il est impossible de déboguer. Il est extrêmement difficile de changer la classe de quelque manière que ce soit. Et les macros seront probablement élargies manuellement à l'avenir lorsque vous trouvez des cas uniques et des caisses de bords où une classe particulière créée de cette manière nécessite "une seule chose différente". Ensuite, les modifications futures de la macro n'affectent pas cette classe, à moins que vous ne vous souvienne de mettre à jour également la copie élargie. Si vous pouvez utiliser des modèles C++ pour cela, cela serait préférable. (Ou comme suggéré par les commentateurs utilise une bibliothèque qui sait faire cela.)
L'utilisation de macros pour cela deviendra une horreur de maintenance, car d'autres ont déjà signalé. En tant qu'approche alternative, vous pouvez implémenter un petit générateur de code pour la création du code de la chaudière répétée.
Bien entendu, un générateur de code doit être maintenu ainsi que la macro, mais le principal avantage est, il produit (espérons-le) un code lisible qui sera compilé seul, peut être débogué seul (sans le code de macro interférant), et étendu individuellement, si nécessaire.
Pour de tels générateurs de code, deux approches sont possibles: une simple génération initiale ponctuelle, puis la maintenance manuelle du code généré par la suite ou la prise en charge de la régénération (vous devez donc prendre soin de la séparation du code généré et écrit manuellement). Les deux approches peuvent convenir à votre cas, vous devez décider ce qui convient le mieux.
Cela supporte généralement lorsque le nombre de classes augmentera à l'avenir, le point de rupture peut être quelque part entre 5 et 50 classes, en fonction de la complexité des exigences et de la fréquence à laquelle vous pouvez utiliser le générateur.
class MyClass :
public AbstractBase {
Q_OBJECT
Vous n'écrivez pas le code C++ ici, vous écrivez le code QT. De manière critique, c'est un code qui est d'abord traité par le compilateur QT moc
. Il s'attend à voir Q_OBJECT
macro dans une classe et générera les métadonnées pour cette classe.
Regardons donc votre approche macro:
class CLASS_NAME : public AbstractBase { \
Q_OBJECT \
Même si moc
repérerait le Q_OBJECT
, il générerait des métadonnées pour CLASS_NAME
. Cela n'est évidemment pas le nom de la classe, c'est un paramètre macro. Le problème est que moc
ne peut pas comprendre toutes les instanciations macro-cibles et ne peut donc pas générer les métadonnées.
Le problème fondamental ici est que vous travaillez avec un modèle de compilation mis en scène, avec un prétraiteur QT et un prétraiteur C. L'une des réalisations de C++ était que le préprocesseur C était gênant d'une perspective d'ingénierie logicielle. C'est pourquoi les modèles C++ font partie intégrante de la langue.
Cela ne vous aide pas avec QT ou vos propres hacks de préprocesseur, cependant. Vous devez vivre avec le fait que moc
fonctionne en premier.