2010-06-21 10 views
11

Je ne suis pas sûr que le titre de la question soit exact ... Permettez-moi de commencer par expliquer mon scénario simple original, puis passer à autre chose pour expliquer ce que je voudrais faire, mais ne peut pas.Polymorphisme de type retour pour la valeur de passage

A l'origine, j'avais quelque chose comme:

class Operand; 

Operand genOperandA() { ...; return Operand(); } 
Operand genOperandB() { ...; return Operand(); } 
... // more operand-generation functions 

typedef Operand (*OpGen)(); 

// Table of function pointers 
static const OpGen generators[] = 
{ 
    genOperandA, 
    genOperandB, 
    ... 
}; 

// Function to do some operation on the operand 
void operate(Operand& op); 

... 

// Example call 
operate(generators[1]()); 

Jusqu'à présent si bon (je pense). Cependant, il existe maintenant plusieurs types d'opérandes dérivés, par ex. class RegisterOperand : public Operand. J'ai de nouvelles fonctions dédiées genOperand qui renverraient idéalement des instances des types dérivés. Mais je ne peux pas le faire:

Operand genOperandC() { ...; return RegisterOperand(); } 

et je ne peux pas le faire:

RegisterOperand genOperandC() { ...; return RegisterOperand(); } 

static const OpGen generators[] = 
{ 
    ... 
    genOperandC, 
}; 

Cependant, je sais que cela fonctionnerait si je devais retourner les types de référence ou pointeur, la seule option que j'ai actuellement est quelque chose comme:

Operand *genOperandC() { ...; return new RegisterOperand(); } 

qui nécessite maintenant un nettoyage explicite qui n'était pas nécessaire à l'origine.

Des alternatives que je n'ai pas envisagées?

Répondre

4

Il peut y avoir d'autres conceptions qui n'exigent pas que vous utilisiez des pointeurs, mais si vous voulez ou voulez aller de cette façon, cela pourrait vous intéresser.


Si vous retournez un pointeur est un problème (en raison de la nécessité de choses « nettoyage »), vous devriez certainement envisager d'utiliser des pointeurs intelligents comme type de retour.

Voici un exemple de votre méthode d'usine avec des pointeurs intelligents:

boost::shared_ptr<Operand> genOperandC() 
{ 
    return boost::shared_ptr<Operand>(new RegisterOperand()); 
} 

De cette façon, vous ne devez appeler delete manuellement: ce sera fait par le destructor de boost::shared_ptr<Operand> pour vous en cas de besoin.

Si après vous devez lancer le pointeur résultant, boost fournit des fonctions de coulée ainsi:

boost::shared_ptr<Operand> op = genOperandC(); 

boost::shared_ptr<RegisterOperand> rop = 
    boost::dynamic_pointer_cast<RegisterOperand>(op); 
+1

Utilisez boost: make_shared plutôt que de créer vous-même le pointeur partagé, c'est une méthode wrapper simple mais réduisez le nombre d'allocations de mémoire nécessaires (et donc plus rapide) –

7

Vous pouvez envelopper:

class Operand 
{ 
public: 

private: 
    std::unique_ptr<OperandImpl> mImpl; 
}; 

Ceci est similaire à un modèle de stratégie: l'opérande réelle le comportement est masqué et accessible via une interface non virtuelle. L'utilisateur reçoit une copie de Operand, elle n'a pas besoin de savoir quoi que ce soit sur son interne et peut l'utiliser, et vous êtes libre d'implémenter divers comportements dérivés.

+0

Est-ce l'idée ici que mes différents sous-types hériteraient 'OperandImpl', et ce membre appelle à' Operand' devrait simplement être délégué à '* mImpl'? –

+0

Oui, il s'agit d'un modèle de stratégie classique dans lequel le traitement de la mémoire est masqué par le client (au lieu de renvoyer un pointeur, intelligent ou non, à 'OperandImpl'). Vous pouvez cependant faire un peu de travail avant de déléguer (vérification des arguments, consignation, etc ...) –

0

Que diriez-vous de quelque chose comme ceci? Notez que vous ne pouvez pas simplement faire fonctionner (générateurs [i]()) comme l'original opèrent() prendre une référence non-const.

#include <iostream> 
#include <string> 
#include <memory> 

class Operand { 
public: 
     Operand(std::string x = "Operand"): x(x) {} 
     const std::string x; 
}; 

class RegisterOperand: public Operand { 
public: 
     RegisterOperand(std::string x = "RegisterOperand") 
       : Operand(x) {} 
}; 

typedef std::auto_ptr<Operand> POperand; 

POperand genOperandA() { return POperand(new Operand("genOperandA")); } 
POperand genOperandB() { return POperand(new Operand("genOperandB")); } 
POperand genOperandC() { return POperand(new RegisterOperand()); } 
// more operand-generation functions 

typedef POperand (*OpGen)(); 

// Table of function pointers 
static const OpGen generators[] = 
{ 
     genOperandA, 
     genOperandB, 
     genOperandC, 
}; 

void operate(const POperand& op) 
{ 
     std::cout << op->x << std::endl; 
} 

int main() 
{ 
     operate(generators[0]()); 
     operate(generators[1]()); 
     operate(generators[2]()); 
     return 0; 
} 
+0

L'utilisation de 'auto_ptr' est obsolète en C++ 0x. Préférez utiliser 'unique_ptr' à la place. Si vous n'avez pas encore accès à C++ 0x, préférez 'shared_ptr'. –

+1

@Matthieu: Bien que j'aime beaucoup la fonction unique_ptr, j'hésiterais à l'utiliser car elle repose sur une fonctionnalité de langage C++ 0x. Je pense que c'est bon d'utiliser std :: auto_ptr si vous savez ce que vous faites et ne voulez pas le surcoût de comptage de références de shared_ptr. Juste mes 2 cents. – sellibitze

0

Je sais que cette question a été posée il y a quelque temps mais je récemment tombé sur ce problème moi-même et je suis venu avec une autre solution que je pourrais être bien utile ici.Donc l'idée est de faire un wrapper pour gérer le pointeur mais aussi de supporter la copie du wrapper contrairement au pointeur unique qui ne peut être déplacé.

class PolymorphicType { 
public: 
    /* 
     Class interface ... 
    */ 
    PolymorphicType() { it = nullptr; } 
    virtual ~PolymorphicType() { if(it != nullptr) delete it; }   

    PolymorphicType& operator=(const PolymorphicType& org) { 
      //Clone the derived type and store it 
      if(org.it != nullptr) 
       it = org.it->clone(); 
    } 
private: 
    Base* it; 
}; 

Chaque classe dérivée doit maintenant implémenter sa propre méthode de clonage et vous êtes prêt! Et juste au cas où voici un bon post expliquant comment le clonage de types dérivés fonctionne: Copying derived entities using only base class pointers, (without exhaustive testing!) - C++

Espérons que cela aide quelqu'un!