2009-07-26 6 views
3

Supposons que je travaille sur une bibliothèque fonctionnant avec des éléments de type Item. Le principal point d'entrée est une classe comme:Implémentation de la comparaison à l'aide d'une interface générique en C++

class Worker 
{ 
private: 
    SomeContainer _c; 
public: 
    void add(const Item &i); 
    void doSomething(); 
}; 

La méthode doSomething() examine les éléments ajoutés, les compare, etc., et fait quelque chose avec eux. La classe Item a donc besoin d'un operator==.

Maintenant, je veux utiliser cette bibliothèque dans différents environnements et dans chaque environnement l'implémentation de la classe Item est différente. Par conséquent, Item nécessite une fonction de comparaison virtuelle:

class Item 
{ 
protected: 
    virtual bool _equals(Item &other) = 0; 
public: 
    bool operator==(Item &other) { return _equals(other); }; 
}; 

Chaque environnement possède sa propre implémentation Item. La bibliothèque ne connaît que la classe Item et les classes d'éléments spécifiques sont définies et implémentées dans les applications spécifiques à la plateforme utilisant la bibliothèque. Dans l'environnement A ce pourrait être:

class AItem: public Item 
{ 
private: 
    bool _equals(Item &other); 
    std::string _member; 
... 
}; 

et dans un environnement B:

class BItem: public Item 
{ 
private: 
    bool _equals(Item &other); 
    int _member; 
... 
}; 

Quelle est maintenant la meilleure façon de, pour chaque environnement, mettre en œuvre la comparaison pour une utilisation par la bibliothèque? _equals étant spécifié dans la classe Item, ses implémentations d'éléments spécifiques doivent convertir other en leur propre type.

Dans un environnement donné, différents types d'éléments ne seront pas utilisés en même temps, donc compte tenu de cette hypothèse, ce qui suit serait tri de sécurité:

bool AItem::_equals(Item &other) 
{ 
    return this->_member == static_cast<AItem &>(other)._member; 
} 

Mais il semble comme une solution méchant parce que il permet à un programmeur utilisant la bibliothèque d'implémenter un nouvel environnement pour casser des choses s'il ajoute des éléments de types différents au travailleur.

D'autres solutions que je peux penser sont:

  • Utilisez dynamic_cast.
  • Implémentez mon propre type de RTTI en ajoutant un membre à la classe d'élément de base indiquant l'environnement. Cela peut ensuite être utilisé dans la fonction de comparaison pour voir si other est du même type. Pas très élégant.
  • Implémentez tous les types dans la bibliothèque et sélectionnez le bon en fonction d'un #define au moment de la compilation. Cela signifie que la bibliothèque elle-même doit être étendue pour chaque environnement.
  • Utilisez un travailleur sous forme de matrice.

Mais je pense qu'il doit y avoir une solution plus élégante. Peut-être quelque chose de complètement différent. Qu'est-ce que tu ferais?

+1

Votre dernière option "Utiliser un travailleur sous-calibré" serait la solution la meilleure et la plus élégante. Il a aussi l'avantage de la meilleure performance car il évite les appels virtuels lors de la comparaison. – mmmmmmmm

+2

Pas pertinent à votre question, mais vous ne devriez vraiment pas utiliser les noms commençant par des traits de soulignement. Les règles pour les quelques cas où ils sont légaux sont terriblement compliquées. Un trait de soulignement suivi d'une lettre majuscule ou un trait de soulignement double est toujours réservé par l'implémentation. Un trait de soulignement suivi de minuscules est réservé dans l'espace de nom global (donc techniquement légal ici, mais cela évite beaucoup de problèmes pour prendre l'habitude de ne pas utiliser de soulignement) – jalf

+1

Ceci est une question très pertinente: seulement quelques-uns semblent se rendre compte que l'égalité n'est pas si simple. – xtofl

Répondre

4
  • N'est-ce pas mieux si Worker a été modélisé sur Item au lieu de s'appuyer sur l'héritage? En général, la valeur sémantique (et l'égalité est à la terre de la valeur sémantique) ne fonctionne pas bien avec l'héritage. Dans votre exemple spécifique, je crains aussi la troncation si SomeContainer copie ce qu'il contient. Si vous avez vraiment besoin d'une opération qui dépend du type dynamique de deux objets, vous devriez faire une recherche sur "envoi multiple" (ou regarder dans "Moderne C++ Design", il y a un chapitre sur ce sujet).Il existe plusieurs techniques connues pour cela avec des compromis différents. L'un des plus connus est souvent associé au modèle de visiteur. La variante la plus simple dépend de la connaissance de tous les descendants d'Item. (Si vous regardez une description du modèle de visiteur, tenez compte du fait que les deux hiérarchies de ce modèle seraient unifiées pour votre application).

Edit: voici le croquis d'un exemple:

class ItemA; 
class ItemB; 

class Item 
{ 
public: 
    virtual bool equal(Item const&) const = 0; 
protected: 
    virtual bool doesEqual(ItemA const*) const { return false; } 
    virtual bool doesEqual(ItemB const*) const { return false; } 
}; 

class ItemA: public Item 
{ 
public: 
    virtual bool equal(Item const& other) const 
    { 
     return other.doesEqual(this); 
    } 
protected: 
    virtual bool doesEqual(ItemA const* other) { 
     return do_the_check; 
    } 
}; 

class ItemB: public Item 
{ 
public: 
    virtual bool equal(Item const& other) const 
    { 
     return other.doesEqual(this); 
    } 
protected: 
    virtual bool doesEqual(ItemB const* other) { 
     return do_the_check; 
    } 
}; 

bool operator==(Item const& lhs, Item const& rhs) 
{ 
    return lhs.equal(rhs); 
} 
+0

OK, c'est un très bon compromis car bien que la bibliothèque ait besoin de connaître les classes, elles peuvent être implémentées dans les applications spécifiques en utilisant la bibliothèque. Bon point sur la copie! La classe Item aura besoin d'une fonctionnalité de clonage virtuel, je suppose, ainsi que d'un destructeur virtuel. – Marten

0

AMHA, l'égalité dépend du contexte. Cela signifie que l'environnement A considérera deux objets égaux sur des bases totalement différentes de l'environnement B. Par conséquent, je pense que l'environnement devrait fournir la fonction d'égalité.

Un autre aspect de l'égalité est qu'elle est généralement considérée comme une opération symétrique: si a est égal à b, b est égal à a. Cela implique qu'il ne peut pas être à répartition unique, c'est-à-dire implémenté en tant que méthode de classe. En conséquence, il doit être dépendant uniquement des interfaces des deux objets. Cela impose également l'utilisation d'une fonction d'égalité explicite à deux arguments fournie par l'environnement.

+0

Exactement raison, les implémentations de tests d'égalité sont très différentes. Donc, vous dites qu'il devrait y avoir une fonction d'égalité en dehors de toute classe dans chaque environnement spécifique? S'il est donné à l'objet Worker comme, disons, un pointeur de fonction, le Worker doit toujours connaître la signature de la fonction. Et comme il ne connaît que la classe Item, les arguments ne peuvent être que de type Item. Donc, à l'intérieur de la fonction d'égalité, les éléments doivent être moulés au bon type et le problème est essentiellement le même, n'est-ce pas? – Marten

+0

En effet, la fonction d'égalité consistera probablement en une interface bool (* eq) (Item, Item). Bien que je puisse imaginer un système où l'environnement demande un objet "Typed" Worker (par exemple en utilisant une fonction de membre de modèle) qui utilise une fonction d'égalité type-safe plus spécifique. – xtofl

Questions connexes