2010-10-23 25 views
2

Dans la hiérarchie d'héritage par exemple trivial:Copier proprement une instance d'une baseclass ou d'une sous-classe en C++?

class Food 
{ 
    virtual ~Food(); 
}; 

class Fruit : public Food 
{ 
    virtual ~Fruit(); 
}; 

class Apple: public Fruit 
{ 
    virtual ~Apple(); 
} 

class Vegetable: public Food 
{ 
    virtual ~Vegetable(); 
} 

Je souhaite créer une méthode qui peut cloner un objet de sa sous-classe ou une instance baseclass:

Apple* apple1 = new Apple(); 
Apple* clone1 = apple1->clone(); 

Food* food1 = apple1; 
Apple* clone2 = food1->clone(); 

Je vois quelques solutions possibles au problème :

  • Utilisez le polymorphisme pour créer une fonction virtuelle explicitement définie pour chaque sous-classe.

  • Utilisez une méthode de fabrique basée sur un modèle pour prendre une instance d'une classe et le type de la sous-classe pour créer la nouvelle instance.

  • Enregistrez un identifiant avec chaque classe et sous-classe stockée ailleurs pour rechercher la sous-classe à créer lors de l'appel d'une fonction de clonage baseclass.

Aucune de ces semble idéal, mais je me penche plus vers la troisième solution, car il simplifie d'appeler la fonction clone sans nécessiter une définition à écrire pour toutes les sous-classes (pour lesquelles il y aura beaucoup).

Cependant, je suis très ouvert à toutes les suggestions, y at-il de meilleures façons de le faire?

Répondre

2

Vous pouvez utiliser le protocole CRTP pour implémenter automatiquement une méthode de clonage.

template<typename T, typename Derive> class CloneImpl : public Derive { 
public: 
    virtual Derive* clone() { 
     return new T(static_cast<const T&>(*this)); 
    } 
}; 
class Food { 
public: 
    virtual Food* clone() = 0; 
    virtual ~Food() {} 
}; 
class Fruit : public Food { 
}; 
class Dairy : public Food { 
}; 
class Apple : public CloneImpl<Apple, Fruit> { 
}; 
class Banana : public CloneImpl<Banana, Fruit> { 
}; 
class Cheese : public CloneImpl<Cheese, Dairy> { 
}; 
class Milk : public CloneImpl<Milk, Dairy> { 
}; 

Dans ce cas, vous pouvez toujours appeler à copier l'objet courant Clone() avec une nouvelle répartition sur le tas et vous n'êtes pas obligé de le mettre en œuvre à nouveau dans toutes les catégories. Bien sûr, si votre sémantique Clone doit être différente, vous pouvez simplement modifier la fonction.

Non seulement le CRTP peut implémenter clone() pour vous, il peut même le faire entre différentes hiérarchies d'héritage.

+0

@DeadMG: votre solution ne semble pas fonctionner avec la hiérarchie des classes de l'OP. Pourriez-vous montrer comment vous abordez la question de cette hiérarchie, Food <- Fruit <- Apple, afin que toutes les classes soient clonables? –

+0

@Alf: Vous avez raison en ce que j'ai raté la classe intermédiaire Fruit, donc je vais modifier. – Puppy

+0

@DeadMG: dans l'exemple de l'OP, ce sont des classes (apparemment) concrètes. Maintenant, ils sont abstraits. Un exemple pratique plus concret d'une hiérarchie de classes concrètes où le clonage est nécessaire (en C++ 98) est une hiérarchie de classes d'exceptions. Alors, pourriez-vous peut-être aussi rendre les classes concrètes? Alors que toutes les classes sont clonables? Cheers, –

-3

Votre exemple ...

Food* food1 = dynamic_cast<Food*>(apple1); 
Apple* clone2 = f1->clone(); 

... ne fonctionnera pas, même avec le eror de speling corrigé. Vous avez besoin de la distribution dans l'autre sens:

Food* food1 = apple1; 
Apple* clone2 = dynamic_cast<Apple*>(f1->clone()); 

En dehors de cela, la solution pratique pour le clonage en C++ est de définir une macro:

#define YOURPREFIX_IMPLEMENT_CLONING(Class)      \ 
    virtual Class*             \ 
     virtualCloneThatIsUnsafeToCallDirectly() const    \ 
    {                \ 
     assert(typeid(*this) == typeid(Class));    \ 
     return new Class(*this);         \ 
    }                \ 
                    \ 
    OwnershipPtr<Class>           \ 
     clone() const            \ 
    {                \ 
     return OwnershipPtr<Class>(        \ 
      virtualCloneThatIsUnsafeToCallDirectly()    \ 
      );              \ 
    } 

... où OwnershipPtr pourrait être, par exemple std::auto_ptr.

Ensuite, tout ce que vous avez à faire est de placer un appel de macro dans chaque classe qui devrait supporter le clonage. Simple.

Il est également possible de mettre en œuvre un clonage réutilisable via un modèle, mais c'est plus compliqué à la fois pour l'implémentation et l'utilisation. Vous pouvez lire à ce sujet dans mon blog en publiant "3 ways to mix in a generic cloning implementation". La conclusion est, cependant, que la macro est la plus pratique.

+1

Non, la solution pratique n'est certainement pas une macro. – Puppy

+0

@DeadMG, avez-vous des arguments à l'appui de cette vue? Par exemple. quelque chose de plus propre et pratique à utiliser? Je serais très intéressé, tout comme toute la communauté C++. :-) Cheers & hth., –

+0

@DeadMG, PS, si vous êtes incapable de trouver quelque chose de plus pratique, s'il vous plaît supprimer le downvote. TIA., –