2010-01-18 5 views
4

Est-ce une mauvaise conception de vérifier si un objet est d'un type particulier en ayant une sorte de membre de données d'identification?Vérification C++ du type de référence

class A 
{ 

private: 
    bool isStub; 
public: 
A(bool isStubVal):isStub(isStubVal){} 
bool isStub(){return isStub;} 


}; 

class A1:public A 
{ 
public: 
A1():A(false){} 
}; 

class AStub:public A 
{ 
public: 
AStub():A(true){} 
}; 

EDIT 1: problème est titulaire d'un grand nombre de fonctions virtuelles, qui A1 ne remplace pas mais le besoin stub, pour indidicating que vous travaillez sur un talon au lieu d'un objet réel. Ici la maintenabilité est la question, pour chaque fonction que j'ajoute à A, j'ai besoin de la surcharger en stub. l'oublier signifie un comportement dangereux lorsque la fonction virtuelle de A est exécutée avec les données de stub. Bien sûr, je peux ajouter un ABase de classe abstraite et laisser A et Astub en hériter. Mais le design est devenu assez rigide pour permettre ce refactor. Un titulaire de référence à A est maintenu dans une autre classe B. B est initialisé avec la référence stub, mais plus tard selon certaines conditions, le détenteur de référence dans B est réinitialisé avec A1, A2 etc. Donc quand je fais cela BObj. GetA(), je peux vérifier dans GetA() si le référent détient un talon et ensuite donner une erreur dans ce cas. Ne pas faire cela signifie que je devrais remplacer toutes les fonctions de A dans AStub avec les conditions d'erreur appropriées.

Répondre

14

Généralement, oui. Vous êtes à moitié OO, moitié procédurale. Qu'allez-vous faire une fois que vous déterminez le type d'objet?

Vous devriez probablement mettre ce comportement dans l'objet lui-même (peut-être dans une fonction virtuelle), et différentes classes dérivées implémentent ce comportement différemment. Vous n'avez alors aucune raison de vérifier le type d'objet.

Dans votre exemple spécifique, vous avez une classe "stub". Au lieu de faire ...

if(!stub) 
{ 
    dosomething; 
} 

Il suffit d'appeler

object->DoSomething(); 

et ont le implemention dans AStub être un vide

+0

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). –

+0

(+1) comme la moitié de la classification semi-procédurale OO: P qui me résume bien. + c'est une réponse élégante. –

3

Généralement oui. Habituellement, vous ne voulez pas interroger l'objet, mais vous attendre à ce qu'il FONCTIONNE comme il convient. Ce que vous suggérez est fondamentalement un RTTI primitif, et ceci est généralement mal vu, à moins qu'il n'y ait de meilleures options.

La méthode OO serait de Stub la fonctionnalité, ne pas vérifier pour cela. Cependant, dans le cas d'un grand nombre de fonctions à "stub" cela peut ne pas sembler optimal. Par conséquent, cela dépend de ce que vous voulez réellement que la classe fasse.

Notez également que, dans ce cas, vous ne perdez pas d'espace:

class A 
{ 
    public: 
    virtual bool isStub() = 0; 
}; 

class A1:public A 
{ 
    public: 
    virtual bool isStub() { return false; }; 
}; 

class AStub:public A 
{ 
    public: 
    virtual bool isStub() { return true; }; 
}; 

... buuut vous avez une fonction virtuelle - ce qui est généralement pas un problème, à moins que c'est un goulot d'étranglement.

+1

L'entrée vtable occupera au moins autant d'espace qu'un 'bool'. –

+1

@ P-Nuts: A moins qu'il n'utilise déjà des tables virtuelles, ce qui serait une hypothèse forte dans le cas d'une classe qui a une implémentation et un stub. –

+0

@ P-Nuts ... dans 99% des cas, on s'en fout? –

0

Avec les classes polymorphes vous pouvez utiliser l'opérateur typeof pour effectuer RTTI. La plupart du temps, vous ne devriez pas besoin de le faire. Sans polymorphisme, il n'y a pas de possibilité de langage pour le faire, mais vous devriez en avoir encore moins souvent.

+0

Vous ne pouvez pas appeler une fonction sur une pile de bits bruts de toute façon, il est donc difficile (pas impossible) d'entrer dans cette situation sans polymorphisme. –

+0

Pas dur du tout: class Base {/ * aucune méthode virtuelle ici * /}; classe Derived: public Base {}; void f (Base b) { /* aucun moyen de dire si appelé avec un objet de type Derived */ } –

1

Si vous souhaitez connaître le type d'objet lors de l'exécution, vous pouvez utiliser dynamic_cast. Vous devez avoir un pointeur ou une référence à l'objet, puis vérifier le résultat de la diffusion dynamique. Si ce n'est pas NULL, alors l'objet est le type correct.

+0

"Si possible" est trop fort et conduit à des gens qui l'évitent quand c'est exactement ce dont ils ont besoin. Évitez les RTTI lorsque cela n'est pas nécessaire. –

+0

@Roger Pate: Et des suggestions comme "quand c'est inutile" mèneront les gens à penser qu'ils en ont besoin quand ils ne le font pas. Nous ne pouvons pas gagner. –

+0

@David: Nous ne pouvons pas battre l'ignorance, c'est vrai, mais "à tout possible" dit "s'il y a un autre moyen, faites-le à la place" plutôt que de demander "est-ce que nous en avons besoin?" –

0

Une mise en garde. Évidemment, votre type va être déterminé au moment de la construction.Si votre détermination de 'type' est une quantité dynamique, vous ne pouvez pas résoudre ce problème avec le système de type C++. Dans ce cas, vous devez avoir une certaine fonction. Mais dans ce cas, il est préférable d'utiliser le comportement overridable/dynamic comme suggéré par Terry. Pouvez-vous fournir une meilleure information que ce que vous essayez d'accomplir?

0

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.

0

Dans le cas général ce n'est peut-être pas une bonne conception, mais dans certains cas, il est raisonnable de choisir une méthode isStub() pour l'utilisation d'un client spécifique qui aurait autrement besoin d'utiliser RTTI. Un tel cas est le chargement paresseux:

class LoadingProxy : IInterface 
{ 
private: 
    IInterface m_delegate; 

    IInterface loadDelegate(); 

public: 
    LoadingProxy(IInterface delegate) : m_delegate(delegate){} 

    int useMe() 
    { 
     if (m_delegate.isStub()) 
     { 
      m_delegate = loadDelegate(); 
     } 

     return m_delegate.useMe(); 
    } 
}; 

Le problème avec RTTI est qu'il est relativement coûteux (lent) par rapport à un appel de méthode virtuelle, de sorte que si votre fonction useMe() est simple/rapide, RTTI détermine la performance. Dans une application sur laquelle j'ai travaillé, l'utilisation de tests RTTI pour déterminer si le chargement paresseux était nécessaire était l'un des goulots d'étranglement de performance identifiés par le profilage.

Cependant, comme beaucoup d'autres réponses l'ont dit, le code de l'application ne devrait pas avoir à se soucier de savoir s'il possède un stub ou une instance utilisable. Le test devrait être dans un endroit/couche dans l'application. À moins que vous n'ayez besoin de plusieurs implémentations de LoadingProxy, il pourrait être utile de faire de isStub() une fonction amie.

Questions connexes