2010-07-15 2 views
17

J'ai le code comme ci-dessous. J'ai une classe de modèle abstrait Foo et deux sous-classes (Foo1 et Foo2) qui dérivent des instanciations du modèle. Je souhaite utiliser des pointeurs dans mon programme pouvant pointer vers des objets de type Foo1 ou Foo2, d'où j'ai créé une interface IFoo.Création d'une interface pour un modèle de classe abstraite en C++

Mon problème est que je ne suis pas sûr de savoir comment inclure la fonctionB dans l'interface, car elle dépend de l'instanciation du modèle. Est-il même possible de rendre la fonction B accessible via l'interface, ou est-ce que je tente l'impossible?

Merci beaucoup pour votre aide.

class IFoo { 
    public: 
     virtual functionA()=0; 

}; 

template<class T> 
class Foo : public IFoo{ 
    public: 
     functionA(){ do something; }; 
     functionB(T arg){ do something; }; 
}; 

class Foo1 : public Foo<int>{ 
... 
}; 

class Foo2 : public Foo<double>{ 
... 
}; 

Répondre

0

Je ne pense pas que vous pouvez obtenir ce que vous voulez. Pensez à ceci si vous implémentez votre suggestion: si vous avez un pointeur vers une instance IFoo et que vous appelez le functionB(), quel paramètre de type devriez-vous lui donner? Le problème sous-jacent est que Foo1::functionB et Foo2::functionB ont des signatures différentes et font des choses différentes.

+0

Mais techniquement, je ne créerons pas une instance de IFoo, Je souhaite seulement créer une instance de Foo1 (ou 2) (pointé par un pointeur IFoo) et j'ai supposé que la fonction appropriée pourrait être trouvée par la limite dynamique? – bishboshbash

+0

@bishboshbash: Pour qu'une fonction soit trouvée par liaison dynamique, tous les types d'arguments doivent être connus; sinon, il serait difficile de savoir ce qu'il faut rechercher si vous avez utilisé des fonctions surchargées. – liori

+0

Je pense que je comprends. Si je créais l'instance de Foo1 et appelait Foo1.functionB (..) cela fonctionnerait bien, puisque le template a été instancié. Puis-je créer une classe abstraite pour pointer vers des objets de type Foo1 ou Foo2 via l'héritage multiple et éviter ainsi le problème de la liaison dynamique tentant de passer à travers un modèle non instancié? – bishboshbash

4

Le moyen le plus simple est de rendre votre interface modélisée.

template <class T> 
class IFoo { 
    public: 
     virtual void functionA()=0; 
     virtual void functionB(T arg){ do something; }; 
}; 

template<class T> 
class Foo : public IFoo<T>{ 
    public: 
     void functionA(){ do something; }; 
     void functionB(T arg){ do something; }; 
}; 
+2

Cela signifierait que je ne pouvais pas créer de pointeurs de type IFoo et les faire pointer vers des instanciations de Foo1 ou Foo2. – bishboshbash

+2

C'est exact, et c'est un problème fondamental ici. Le type de l'argument que 'functionB' prend dépend du type de modèle instancié et ** doit ** être connu au moment de la compilation. –

+0

@bishboshbash votre exigence est mal formée en premier lieu. Foo1 et Foo2 ont des classes de base différentes (IFoo et IFoo sont deux types différents). – h9uest

4

Depuis le type d'argument de functionB doit être connu à l'avance, vous avez un seul choix: Faites un type qui peut contenir tous les arguments possibles. Ceci est parfois appelé un "type supérieur" et les bibliothèques boost ont le type any qui est assez proche de ce que ferait un type supérieur. Voici ce qui pourrait fonctionner:

#include <boost/any.hpp> 
#include <iostream> 
using namespace boost; 

class IFoo { 
    public: 
    virtual void functionA()=0; 
    virtual void functionB(any arg)=0; //<-can hold almost everything 
}; 

template<class T> 
class Foo : public IFoo{ 
    public: 
     void functionA(){ }; 
     void real_functionB(T arg) 
     { 
     std::cout << arg << std::endl; 
     }; 
     // call the real functionB with the actual value in arg 
     // if there is no T in arg, an exception is thrown! 

     virtual void functionB(any arg) 
     { 
      real_functionB(any_cast<T>(arg)); 
     } 
}; 

int main() 
{ 
    Foo<int> f_int; 
    IFoo &if_int=f_int; 

    if_int.functionB(10); 

    Foo<double> f_double; 
    IFoo &if_double=f_double; 
if_int.functionB(10.0); 

} 

Malheureusement, any_cast ne connaît pas les conversions habituelles. Par exemple, any_cast<double>(any(123)) lève une exception car il n'essaie même pas de convertir l'entier 123 en double. Si cela ne concerne pas les conversions, car il est impossible de toutes les répliquer de toute façon. Il y a donc quelques limitations, mais il est possible de trouver des solutions de contournement si nécessaire.

+1

Pour utiliser cette réponse, l'appelant ('main()') doit connaître le sous-type réel d'une instance de 'IFoo' (' if_int' et 'if_double') afin qu'il puisse transmettre les arguments corrects. Si cela se passe mal, une exception d'exécution est levée! Alors, pourquoi ne pas utiliser l'appel simplement 'dynamic_cast'? – Karmastan

+1

Si j'ai l'appelant à utiliser dynamic_cast, alors j'ai besoin de lui pour savoir Foo , Foo . Mais la bonne chose à propos d'une interface abstraite est, que je n'ai besoin de dire à personne, comment l'interface est réellement implémentée. Il peut s'agir d'un type tiers privé provenant d'une bibliothèque partagée ou d'un type local dans un corps de fonction. En outre, c'est uniquement la limitation de boost de any_cast qu'il lance pour le mauvais type d'argument. Vous êtes libre d'améliorer ses fonctionnalités, de sorte qu'il effectue les bonnes conversions pour les types intrinsèques par exemple. Cependant, ce qui ne peut pas être fait, c'est d'attraper ** toutes ** les conversions valides. –

9

Vous tentez l'impossible.

Le cœur même de la question est simple: virtual et template ne se mélangent pas bien.

  • template concerne la génération de code à la compilation. Vous pouvez le considérer comme une sorte de macros typées + quelques astuces saupoudrées pour la programmation méta.
  • virtual est à propos de la décision d'exécution, et cela nécessite un peu de travail.

virtual est généralement implémenté en utilisant une table virtuelle (pensez à une table qui liste les méthodes). Le nombre de méthodes doit être connu au moment de la compilation et est défini dans la classe de base. Cependant, avec vos besoins, nous aurions besoin d'une table virtuelle de taille infinie, contenant des méthodes pour les types que nous n'avons pas encore vus et qui ne seront définis que dans les années à venir ... c'est malheureusement impossible.

Et si c'était possible?

Eh bien, cela n'aurait aucun sens. Que se passe-t-il lorsque j'appelle Foo2 avec un int? Ce n'est pas fait pour ça!Par conséquent, il rompt le principe que Foo2 implémente toutes les méthodes de IFoo.

Ainsi, il serait mieux si vous avez déclaré le vrai problème, de cette façon nous pourrions vous aider à un niveau de conception plutôt que sur le plan technique :)

+0

Je dois classes Foo1 et Foo2. Ils contiennent tous deux des structures de données similaires: l'une contient des entiers et l'autre contient des nombres rationnels (paires d'entiers). Ils ont de nombreuses méthodes qui sont implémentées de manière identique (par rapport à int/paire de int), qui interagissent avec d'autres types de données (int/pair of int) eux-mêmes. Mais ils ont quelques méthodes qui sont mises en œuvre différemment. Je l'ai implémenté à l'origine avec héritage, mais les structures de données sont définies dans les sous-classes et je ne peux donc pas écrire les fonctions membres qui agissent sur elles dans la classe de base, ce qui conduit à une duplication de code importante. – bishboshbash

+0

J'ai eu les mêmes problèmes plusieurs fois et avec des classes contenant de nombreuses méthodes et propriétés dans leur interface (10+). Ce que j'ai fait était de créer des interfaces pour chaque instance et de réduire le nombre de méthodes utilisant des fermetures, par ex. 'ErrorCode ScalarParameterSet (ParamType t, float ParamValue)' pour unir tous les setters. Si vous voulez, je pourrais donner un exemple de ceci –

Questions connexes