2008-11-03 12 views
23

Comme l'a écrit Scott Myers, vous pouvez profiter d'une détente dans le type système de C++ pour déclarer clone() pour renvoyer un pointeur vers le type réel d'être déclaré: déclarationsQuelle est la meilleure signature pour clone() en C++?

class Base 
{ 
    virtual Base* clone() const = 0; 
}; 

class Derived : public Base 
{ 
    virtual Derived* clone() const 
}; 

Le compilateur détecte que clone() un pointeur sur le type de l'objet et permet à Derived de le surcharger pour renvoyer un pointeur sur derived.

Il serait souhaitable d'avoir clone() renvoie un pointeur intelligent qui implique le transfert de la sémantique de propriété, comme ce qui suit:

class Base 
{ 
    virtual std::auto_ptr<Base> clone() const = 0; 
}; 

class Derived : public Base 
{ 
    virtual std::auto_ptr<Derived> clone() const; 
}; 

Malheureusement, l'assouplissement des conventions ne s'applique pas aux pointeurs intelligents basé sur un modèle, et le compilateur n'autorisera pas le remplacement.

Ainsi, il semble que je suis parti avec deux options:

  1. Avez-clone() renvoie un pointeur « stupide », et le document que les clients sont responsables de l'élimination de celui-ci. Demandez à clone() de renvoyer un pointeur de base intelligent et demandez aux clients d'utiliser dynamic_cast pour les enregistrer dans un pointeur dérivé s'ils en ont besoin.

L'une de ces approches est-elle préférée? Ou y a-t-il un moyen pour moi de manger ma sémantique de transfert de propriété et d'avoir ma sécurité de type forte aussi?

Répondre

5

Cela dépend de votre cas d'utilisation. Si jamais vous pensez avoir besoin d'appeler clone sur un objet dérivé dont vous connaissez le type dynamique (rappelez-vous, le point entier de clone est de permettre la copie sans connaissant le type dynamique), alors vous devriez probablement retourner un pointeur muet et charger cela dans un pointeur intelligent dans le code appelant. Si ce n'est pas le cas, vous n'avez qu'à retourner un smart_ptr et vous pouvez donc le retourner dans tous les remplacements.

+0

En fait, ce qui a déclenché ce que je voulais dire - je pourrais appeler directement nouveau avec le constructeur de copie quand j'avais besoin de cette classe particulière. – JohnMcG

0

Vous pouvez avoir deux méthodes, un clone virtuel() qui renvoie un wrapper de pointeur intelligent autour du type de base et un clone2() non virtuel qui renvoie le type correct de pointeur intelligent. Clone2 serait évidemment implémenté en termes de clone et encapsulerait la fonte.

De cette façon, vous pouvez obtenir le pointeur intelligent le plus dérivé que vous connaissez au moment de la compilation. Ce n'est peut-être pas le type le plus dérivé, mais il utilise toutes les informations disponibles pour le compilateur.

Une autre option serait de créer une version de modèle de clone qui accepte le type que vous attendez, mais qui ajoute plus de fardeau sur l'appelant.

7

Je pense que la sémantique de fonction est si claire dans ce cas qu'il y a peu de place pour la confusion. Donc je pense que vous pouvez utiliser la version covariante (celle qui renvoie un pointeur muet au type réel) avec une conscience facile, et vos appelants sauront qu'ils obtiennent un nouvel objet dont la propriété leur est transférée.

+0

+1, laissez l'utilisateur choisir quelle stratégie de gestion de la mémoire est la meilleure pour lui ... en outre, il est également recommandé pour une utilisation avec Boost Pointer Container :) –

19

La syntaxe n'est pas aussi agréable, mais si vous ajoutez ceci à votre code ci-dessus, cela ne résout-il pas tous vos problèmes?

template <typename T> 
std::auto_ptr<T> clone(T const* t) 
{ 
    return t->clone(); 
} 
+4

Comme ça. Pour que cela ressemble aux choses de la norme, proposez un changement de nom à make_clone() (Like make_pair <>). –

+0

Astuce bonus: en faire un ami, et rendre clone() private. – MSalters

+0

Nitpick: Je voudrais que la fonction accepte à la place 'T const & t' - il n'est pas nécessaire d'utiliser un pointeur ici. A part ça, j'aime vraiment cette approche. – cdhowie

1

C'est l'une des raisons d'utiliser boost::intrusive_ptr au lieu de shared_ptr ou auto/unique_ptr.Le pointeur brut contient le nombre de références et peut être utilisé de manière plus transparente dans des situations comme celle-ci.

2

Tr1::shared_ptr<> peut être coulé comme si c'était un pointeur brut.

Je pense que clone() retourne un pointeur shared_ptr<Base> est une solution assez propre. Vous pouvez afficher le pointeur sur shared_ptr<Derived> au moyen de tr1::static_pointer_cast<Derived> ou tr1::dynamic_pointer_cast<Derived> s'il n'est pas possible de déterminer le type d'objet cloné au moment de la compilation.

Pour assurer le type d'objet est prédictible, vous pouvez utiliser un casting pour polymorphes shared_ptr comme celui-ci:

template <typename R, typename T> 
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p) 
{ 
    assert(std::tr1::dynamic_pointer_cast<R>(p)); 
    return std::tr1::static_pointer_cast<R>(p); 
} 

Les frais généraux ajoutés par le assert sera jeté dans la version.

+0

Quelle est la !! pour? – mmocny

+0

Je pensais qu'il était nécessaire de forcer la conversion implicite à bool: à la place, il est redondant. Merci –

26

Utilisez le modèle virtuel non virtuel/public-privé:

class Base { 
    public: 
    std::auto_ptr<Base> clone() { return doClone(); } 
    private: 
    virtual Base* doClone() { return new (*this); } 
}; 
class Derived : public Base { 
    public: 
    std::auto_ptr<Derived> clone() { return doClone(); } 
    private: 
    virtual Derived* doClone() { return new (*this); } 
}; 
+8

Ou 'std :: unique_ptr <>' depuis C++ 11. – MSalters

1

Mise à jour MSalters answer 14 C++:

#include <memory> 

class Base 
{ 
public: 
    std::unique_ptr<Base> clone() const 
    { 
     return do_clone(); 
    } 
private: 
    virtual std::unique_ptr<Base> do_clone() const 
    { 
     return std::make_unique<Base>(*this); 
    } 
}; 

class Derived : public Base 
{ 
private: 
    virtual std::unique_ptr<Base> do_clone() const override 
    { 
     return std::make_unique<Derived>(*this); 
    } 
} 
+0

Un clone non virtuel? !!! – curiousguy

+0

@curiousguy Oups! Infiniment reconnaissant... – Daniel

Questions connexes