2009-01-27 6 views
5

Supposons que nous ayons la hiérarchie de classe suivante:Faire une copie d'un type de béton inconnu en C++

class Base { 
    ... 
}; 

class Derived1 : public Base { 
    ... 
}; 

class Derived2 : public Base { 
    ... 
}; 

Compte tenu d'un Base* qui pourrait pointer soit un objet Derived1 ou Derived2 comment puis-je faire une copie du réel objet étant donné que son type concret est inconnu. J'ai pensé à définir des constructeurs de copie mais je ne pense pas que cela soit possible sans connaître les types impliqués. La seule solution à laquelle je peux penser est de définir une méthode clone() sur chaque type de la hiérarchie. Quelqu'un peut-il penser à quelque chose de plus élégant?

Répondre

12

Malheureusement un motif clone virtuel/copie est votre seul choix réel.

Il existe des variantes de ceci, mais essentiellement ils se résument tous à des fonctions d'écriture pour chaque type qui peut renvoyer une nouvelle copie.

2

Je ne pense pas que ce serait facile à faire. En fait, efficace C++, Meyers a averti que si vous avez une fonction qui passe par la valeur, et que vous avez

void foo(Base b) 

et vous passez un Derived1 d1 en foo, la copie serait trancha - à savoir la les parties dérivées ne seront pas copiées.

Mais je cite de mémoire au moment ...

4

Vous devez implémenter la méthode de clonage virtuel sur chaque objet de la hiérarchie. Sinon, comment votre objet de type indéfini sait-il comment se copier? Pour que cela soit plus évident pour les personnes qui écrivent des classes dérivées, vous pourriez aussi envisager de rendre la méthode clone abstraite dans votre classe de base, pour forcer les gens à écrire au moins quelque chose.

Dans plusieurs endroits pour mon code, je vérifie également que la méthode to_string() est implémentée correctement pour sérialiser un objet en une chaîne. Le test spécifique que je l'ai utilisé dans ma classe pour tester clone et to_string ressemble simultanément comme ceci:

Base *obj1, *obj2; 
# initialize obj1 in a reasonable manner 
obj2 = obj1->clone(); 
assert(obj1->to_string() == obj2->to_string()); 

Cette teste à la fois la méthode clone() et sérialisation d'objets à cordes (il est donc pas strictement un test unitaire), mais il s'agit d'un paradigme simple à enrouler en boucle autour de tous les objets d'une fabrique pour s'assurer qu'ils respectent une norme minimale d'implémentation de clone() et de to_string().

Éditer: Code mis à jour avec correction du commentaire de strager. Merci!

+0

Je pense que vous voulez dire Base * obj1, * obj2 ;. =] – strager

+0

Bonne prise strager! –

0

Un constructeur de copie n'est pas possible (autant que je sache) car la mémoire est allouée avant le constructeur est appelé. Le constructeur de copie du type connu est utilisé, pas du type de l'objet.

Base *foo = new Derived1(); 
Base bar = *foo;    // Base's copy constructor is called. 

Votre meilleur pari serait de fournir une méthode clone(), comme vous le suggérez.

-1

Une alternative est de créer une usine comme Base, en supposant que vous connaissez toutes vos classes dérivées:

À la réflexion, ce qui est probablement une mauvaise idée!

class Base { 
    public: 
     static Base *clone(const Base *obj) { 
      if((Derived1 *o = dynamic_cast<Derived1 *>(obj)) { 
       return new Derived1(*o); 
      } else if((Derived2 *o = dynamic_cast<Derived2 *>(obj)) { 
       return new Derived2(*o); 
      } 

      /* TODO When more classes are added, put them here. (Probably bad design, yes.) */ 

      return new Base(obj); 
     } 

     Base *clone() const { 
      return clone(this); 
     } 
}; 
+1

Hilarant - pourquoi l'afficher si c'est une si mauvaise idée? – Tom

+0

@Tom, Eh bien, c'est probablement une mauvaise idée. Je suis sûr qu'il y a des cas rares où il peut être utilisé ... – strager

+0

C'est * certainement * une mauvaise idée. Une classe de base devrait * jamais * avoir une connaissance de ses enfants --- c'est l'une des pires violations du principe d'inversion de dépendance. –

0

Vous avez raison de clone(). Vous ne pouvez pas utiliser de constructeurs car ils forcent la liaison statique. Vous ne pouvez pas renvoyer un objet par valeur car cela force à nouveau la liaison statique. Ainsi, vous avez besoin d'une fonction virtuelle qui renvoie un objet alloué dynamiquement.

Questions connexes