Ce genre de chose va bien. Il est généralement préférable de mettre la fonctionnalité dans l'objet, de sorte qu'il n'est pas nécessaire de mettre le type en marche - cela simplifie le code appelant et localise les changements futurs - mais il y a beaucoup à dire pour pouvoir vérifier les types.
Il y aura toujours des exceptions au cas général, même avec la meilleure volonté dans le monde, et être capable de vérifier rapidement le cas spécifique impair peut faire la différence entre avoir quelque chose fixé par un changement dans un endroit, un hack rapide spécifique au projet dans le code spécifique au projet, et avoir à faire des changements plus étendus et invasifs (des fonctions supplémentaires dans la classe de base à tout le moins) - en poussant éventuellement les préoccupations spécifiques au projet dans le code partagé ou framework.
Pour une solution rapide au problème, utilisez dynamic_cast
. Comme d'autres l'ont noté, cela permet de vérifier qu'un objet est d'un type donné - ou un type dérivé de celui-ci (une amélioration par rapport à l'approche simple des "check IDs"). Par exemple:
bool IsStub(const A &a) {
return bool(dynamic_cast< const AStub * >(&a));
}
Cela ne nécessite aucune installation, et sans aucun effort de sa part les résultats seront corrects. Il est également compatible avec les modèles d'une manière très simple et évidente.
Deux autres approches peuvent également convenir.
Si l'ensemble des types dérivés est fixe ou s'il existe un ensemble de types dérivés couramment utilisés, certains peuvent avoir des fonctions sur la classe de base qui exécutera le cast. Les implémentations de classe de base retour NULL
:
class A {
virtual AStub *AsStub() { return NULL; }
virtual OtherDerivedClass *AsOtherDerivedClass() { return NULL; }
};
outrepasser Ensuite, selon le cas, par exemple:
class AStub : public A {
AStub *AsStub() { return this; }
};
Encore une fois, cela permet d'avoir des objets d'un type dérivé traité comme si elles étaient leur type de base - - ou pas, si cela serait préférable. Un autre avantage de ceci est qu'il n'est pas nécessaire de retourner this
, mais peut renvoyer un pointeur vers un autre objet (une variable membre peut-être). Cela permet à une classe dérivée donnée de fournir plusieurs vues de lui-même, ou peut-être de changer son rôle lors de l'exécution.
Cette approche n'est cependant pas spécialement adaptée aux modèles. Cela exigerait un peu de travail, avec le résultat soit un peu plus verbeux ou utilisant des constructions avec lesquelles tout le monde n'est pas familier.
Une autre approche consiste à réifier le type d'objet. Avoir un objet réel qui représente le type, qui peut être récupéré à la fois par une fonction virtuelle et une fonction statique. Pour le contrôle de type simple, ce n'est pas beaucoup mieux que dynamic_cast, mais le coût est plus prévisible sur un large éventail de compilateurs, et les possibilités de stockage de données utiles (nom de classe, informations de réflexion, hiérarchie de classe navigable, etc.) beaucoup plus grand.
Cela nécessite un peu d'infrastructure (quelques macros, au moins) pour faciliter l'ajout des fonctions virtuelles et la gestion des données de la hiérarchie, mais cela donne de bons résultats. Même si cela est seulement utilisé pour stocker les noms de classe qui sont garantis pour être utile, et pour vérifier les types, il va payer pour lui-même.
Avec tout cela en place, la vérification d'un type particulier d'objet pourrait alors aller quelque chose comme cet exemple:
bool IsStub(const A &a) {
return a.GetObjectType().IsDerivedFrom(AStub::GetClassType());
}
(IsDerivedFrom
peut-être déterminés par des tables, ou il pourrait simplement boucle à travers les données hiérarchie. L'une ou l'autre de ces méthodes peut être plus efficace que dynamic_cast
, mais le coût d'exécution approximatif est au moins prévisible.)
Comme avec dynamic_cast, cette approche peut également faire l'objet d'une automatisation avec des modèles.
Cette approche est également plus flexible, car vous pouvez avoir des stubs partiels si vous avez plus d'une fonction (par exemple DoSomething1() est une implémentation de stub, alors que DoSomethingElse() est fonctionnel). –
(+1) comme la moitié de la classification semi-procédurale OO: P qui me résume bien. + c'est une réponse élégante. –