2010-08-18 9 views
10

Je suis assez sûr qu'il n'y a aucun moyen de le faire explicitement mais je voudrais demander sans faute au cas où il y aurait un meilleur moyen. J'ai une classe de base A et une classe B dérivée, maintenant j'ai une liste std :: de A * qui pointe vers B * et je veux copier cette liste de A * dans un std :: vector de B * est donc en gros je veux faire ceci:C++ std :: copie avec le type cast en classe dérivée possible?

std::list<A*> aList = someObject.getAs(); 
std::vector<B*> bVec = std::vector<B*>(aList.begin(), aList.end()); 

Je suis assez sûr que cela devrait compiler lorsque la liste et le vecteur seraient du même type (par exemple, les deux étaient A * 's), mais étant donné que dans ce cas A * est la classe de base de B * Je ne peux pas le faire de cette façon, parce que je dois explicitement cataloguée par exemple comme ceci:

std::list<A*> aList = someObject.getAs(); 
std::vector<B*> bVec; 
bVec.reserve(aList.size()); 
std::list<A*>::iterator it = aList.begin(); 
for(it; it!=aList.end(); ++it) 
{ 
    B* b = static_cast<B*>(*it); 
    bVec.push_back(b); 
} 

est-il un moyen plus élégant que ma deuxième approche ou sera Je dois le faire comme ça?

+0

A cet effet, dynamic_cast est plus sûr que static_cast. static_cast vous donnera un B * même si ce n'est pas un B *, alors que dynamic_cast vous donnera un pointeur nul. Dans les deux cas, si le A * ne pointe pas vers un B, vous obtenez un comportement indéfini si vous le traitez comme un B, mais dans la version dynamic_cast vous pouvez au moins dire si ce n'est pas un B. –

+2

@David Thornley : ... tant que 'A' a au moins une fonction virtuelle. –

+0

@Charles: Merci - bonne prise. –

Répondre

15

La conversion n'est pas implicite, vous devez donc la rendre explicite. L'algorithme standard pour appliquer une sorte de transformation à une séquence est std::transform, que vous pouvez utiliser pour remplir un conteneur vide comme suit:

struct A {}; 
struct B : A {}; 

template <typename From, typename To> 
struct static_caster 
{ 
    To* operator()(From* p) {return static_cast<To*>(p);} 
}; 

std::list<A*> a; 
std::vector<B*> b; 
std::transform(a.begin(), a.end(), std::back_inserter(b), static_caster<A,B>()); 
+0

L'OP devrait aller avec celui-ci à mon humble avis. –

+0

Les identifiants de majuscules sont normalement réservés aux MACRO. – Puppy

+0

Je ne suis pas sûr que ce soit plus élégant que le code de l'OP, mais c'est le mode STL :) Je déteste chercher autour de ce que static_caster fait. – Andrew

7

Définir un foncteur pour effectuer le moulage par exemple.

struct Downcast 
{ 
    B* operator() (A* a) const 
    { 
     return static_cast< B* >(a); 
    } 
}; 

puis utilisez std::transform au lieu de std::copy-à-dire

bVec.resize(aList.size()); 
std::transform(aList.begin(), aList.end(), bVec.begin(), Downcast()); 

Notez que vous pouvez également faire

std::vector<B*> bVec; 
std::transform(aList.begin(), aList.end(), std::back_inserter(bVec), Downcast()); 

auquel cas bVec se développera au besoin, mais je préfère la première approche d'être absolument sûr que l'allocation de mémoire est tout fait à la fois. Comme @Mike Seymour fait remarquer que vous pouvez appeler bVec.reserve(aList.size()) dans le second cas pour assurer une allocation.

+0

Peut aussi faire de Downcast générique. Il faudrait probablement l'appeler aussi "static_downcast", pour se différencier de la version dynamique, vous devrez peut-être écrire plus tard. –

+0

@Noah Roberts: De bons points. – Troubadour

+0

Cela devrait être 'resize()', pas 'reserve()'. Soit cela, soit utiliser 'back_inserter (bVec)' au lieu de 'bVec.begin()' pour pousser les objets au lieu de les écraser. –

1

Vous pourriez adopter l'approche de l'adaptateur d'itérateur, mais je vous suggérerais de le faire correctement si vous le faites. Soit vous devez remplacer tout ce qui fait d'un itérateur un "Iterator", soit utiliser Boost.Iterator, une bibliothèque destinée à faciliter ces choses.

L'autre approche que vous utiliseriez est de faire un foncteur et d'utiliser std :: transform au lieu de std :: copy. Cela me semblerait une approche beaucoup plus facile. Si vous utilisez un compilateur C++ 0x, vous pouvez même utiliser un lambda. Editer: La personne qui a suggéré d'utiliser un adaptateur a tiré sa réponse de sorte que le premier paragraphe pourrait ne pas avoir beaucoup de sens. Il utilisait un wrapper autour des itérateurs vectoriels qui renvoyaient B * au lieu de A * mais il laissait de côté beaucoup de travail pour le faire correctement.

2

Utilisez une transformation:

#include <cstdlib> 
#include <vector> 
#include <algorithm> 
using namespace std; 

class A 
{ 
}; 
class B : public A 
{ 
}; 

A* get_a() { return new B; } 

B* make_b(A* a) { return static_cast<B*>(a); } 

int main() 
{ 
    vector<A*> a_list; 
    vector<B*> b_list; 

    generate_n(back_inserter(a_list), 10, get_a); 
    transform(a_list.begin(), a_list.end(), back_inserter(b_list), make_b); 

    return 0; 
}