2010-07-22 9 views
16

j'ai une classe de base abstraiteméthodes abstraites statiques dans C++

class IThingy 
{ 
    virtual void method1() = 0; 
    virtual void method2() = 0; 
}; 

Je veux dire - « toutes les classes fournissant une instanciation de béton doivent fournir ces méthodes statiques trop »

Je suis tenté de faire

class IThingy 
{ 
    virtual void method1() = 0; 
    virtual void method2() = 0; 
    static virtual IThingy Factory() = 0; 
}; 

Je sais que ne compile pas, et de toute façon il n'est pas clair comment l'utiliser même s'il compile. Et de toute façon je peux juste faire

Concrete::Factory(); // concrete is implementation of ITHingy 

sans mentionner Factory dans la classe de base du tout. Mais je pense qu'il devrait y avoir un moyen d'exprimer le contrat auquel je veux que les implémentations s'inscrivent.

Existe-t-il un idiome bien connu pour cela? Ou est-ce que je viens de le mettre dans les commentaires? Peut-être que je ne devrais pas essayer de le forcer de toute façon

Editer: Je me sentais vague en tapant la question. Je pensais qu'il devrait y avoir un moyen de l'exprimer. Igor donne une réponse élégante mais en fait cela montre que ça n'aide vraiment pas. Je finis toujours par avoir à faire

IThingy *p; 
    if(..) 
     p = new Cl1(); 
    else if(..) 
     p = new Cl2(); 
    else if(..) 
     p = new Cl3(); 
    etc. 

Je suppose que des langues de réflexion comme C#, python ou java pourrait offrir une meilleure solution

+3

Je ne suis pas tout à fait sûr de ce que vous voulez. Êtes-vous à la recherche d'une méthode 'clone'? – GManNickG

+2

Je suppose que je ne vois pas vraiment une situation réelle où vous auriez besoin de quelque chose comme ça? Le polymorphisme n'a vraiment de sens qu'avec les instances réelles. Les méthodes statiques sont simplement des fonctions régulières qui utilisent le nom de classe pour MRO. Cela n'a pas vraiment de sens, à un moment donné, vous devez connaître le nom de la méthode class :: car C++ est lié statiquement. – rossipedia

+1

Il semble qu'il veut spécifier que les sous-classes ont une méthode Factory statique. Et Bryan, vous devriez le déplacer vers l'espace de réponse. – jdmichal

Répondre

27

Le problème que vous rencontrez est en partie lié à une légère violation d'un principe de responsabilité unique. Vous essayiez d'imposer la création de l'objet via l'interface. L'interface devrait plutôt être plus pure et ne contenir que des méthodes qui font partie intégrante de ce que l'interface est censée faire. Au lieu de cela, vous pouvez supprimer la création de l'interface (la méthode virtual static souhaitée) et la placer dans une classe d'usine.

Voici une implémentation en usine simple qui force une méthode d'usine sur une classe dérivée.

template <class TClass, class TInterface> 
class Factory { 
public: 
    static TInterface* Create(){return TClass::CreateInternal();} 
}; 

struct IThingy { 
    virtual void Method1() = 0; 
}; 

class Thingy : 
    public Factory<Thingy, IThingy>, 
    public IThingy { 
     //Note the private constructor, forces creation through a factory method 
     Thingy(){} 
public: 
     virtual void Method1(){} 
     //Actual factory method that performs work. 
     static Thingy* CreateInternal() {return new Thingy();} 
}; 

Utilisation:

//Thingy thingy; //error C2248: 'Thingy::Thingy' : cannot access private member declared in class 'Thingy' 

IThingy* ithingy = Thingy::Create(); //OK 

Par derinving de Factory<TClass, TInterface>, la classe dérivée est forcée d'avoir une méthode CreateInternal par le compilateur. Non deifining cela se traduira par une erreur comme ceci:

erreur C2039: « CreateInternal »: n'est pas membre de

0

Il n'y a aucun moyen sûr de prescrire un tel contrat en C++, comme il y a aussi aucun moyen d'utiliser ce type de polymorphisme, puisque la ligne

Concrete::Factory() 

est toujours une chose compilation statique, qui est, vous ne pouvez pas écrire cette ligne où Concrete serait un client encore fourni inconnue classe.

Vous pouvez faire en sorte que les clients implémentent ce type de "contrat" ​​en le rendant plus pratique que de ne pas le fournir. Par exemple, vous pouvez utiliser CRTP:

class IThingy {...}; 

template <class Derived> 
class AThingy : public IThingy 
{ 
public: 
    AThingy() { &Derived::Factory; } // this will fail if there is no Derived::Factory 
}; 

et dire aux clients de dérivés de AThingy<their_class_name> (vous pouvez appliquer cette gauchir de visibilité du constructeur, mais vous ne pouvez pas garantir que les clients ne mentent pas their_class_name).

Ou vous pouvez utiliser la solution classique, créer une hiérarchie distincte de classes de fabrique et demander aux clients de fournir leur objet ConcreteFactory à votre API.

+0

"vous ne pouvez pas écrire cette ligne où Concrete serait une classe encore inconnue fournie par le client." Vous pouvez, si vous avez un moyen d'enregistrer des constructeurs de type avec l'usine. Je le fais tout le temps où dans ma couche de messagerie ne connaît que mes types abstraits, mais en utilisant une usine peut construire et retourner des instances de types définis dans une autre bibliothèque. –

+0

Désolé, j'aurais dû dire que vous ne pouvez pas le faire directement à partir de 'IThingy' si' Concrete' n'a pas été défini, comme vous l'avez mentionné. Je voulais dire que vous devez donner un moyen à 'IThingy' pour instancier indirectement' Concrete'. –

0

méthodes statiques « Thingy » ne peut pas être fait virtuel (ou abstraite, De toute façon) en C++. Pour faire ce que vous voulez faire, vous pouvez avoir une méthode IThingy::factory qui retourne une instance concrète, mais vous devez fournir un moyen de créer l'instance en usine. Par exemple, définir une signature de méthode comme IThing* (thingy_constructor*)() et avoir un appel statique dans IThingy que vous pouvez passer une telle fonction à qui définit comment IThingy va construire l'instance de l'usine. Ensuite, dans une bibliothèque ou une classe dépendante, vous pouvez appeler cette méthode avec une fonction appropriée qui, à son tour, sait comment construire correctement un objet implémentant votre interface.

En supposant que votre 'initializer' d'usine ne soit pas appelé, vous devez prendre les mesures appropriées, telles que le lancement d'une exception.

Questions connexes