2010-08-18 13 views
10

Jestem prawie pewien, że nie ma sposobu, aby zrobić to wyraźnie, ale chciałbym zapytać, czy nie ma na to lepszego sposobu. Mam klasę podstawową A i pochodną klasy B, teraz mam std :: listę A *, która wskazuje na B * i chcę skopiować tę listę A * do std :: wektor B * „s więc zasadniczo chcę, aby to zrobić:C++ std :: copy z typem rzutowania na klasę pochodną możliwą?

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

Jestem całkiem pewien, że to powinien skompilować gdy lista, a wektor byłoby tego samego typu (np obaj a *” s), ale ponieważ w tym przypadek A * jest klasą bazową B * nie mogę zrobić to w ten sposób, bo musiałbym wyraźnie typecast na przykład tak:

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); 
} 

Czy istnieje bardziej elegancki sposób niż moje drugie podejście czy będzie Muszę to zrobić?

+0

W tym celu dynamic_cast jest bezpieczniejsze niż static_cast. static_cast da ci B * nawet jeśli nie jest B *, a dynamic_cast da ci zerowy wskaźnik. W obu przypadkach, jeśli A * nie wskazuje na B, otrzymujesz niezdefiniowane zachowanie, jeśli traktujesz je jako B, ale w wersji dynamic_cast możesz przynajmniej powiedzieć, czy to nie jest B. –

+2

@ David Thornley : ... o ile 'A' ma co najmniej jedną funkcję wirtualną. –

+0

@Charles: Dzięki - dobry połów. –

Odpowiedz

15

Nie jest bezpiecznie konwersja niejawnie, więc trzeba to wyraźnie określić. Standardowy algorytm stosowaniu pewnego rodzaju transformacji sekwencji jest std::transform, których można użyć, aby wypełnić pusty pojemnik następująco:

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

PO powinien iść z tym jednym IMHO. –

+0

Wszystkie identyfikatory czapek są zwykle zarezerwowane dla MAKRO. – Puppy

+0

Nie jestem pewien, czy jest to bardziej elegancki niż kod PO, ale jest to sposób STL :) Nienawidzę przeszukiwać, aby dowiedzieć się, co robi static_caster. – Andrew

7

Zdefiniuj funktora do wykonywania rzutowania, np.

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

a następnie użyć std::transform zamiast std::copy tj

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

Uwaga Można również zrobić

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

w takim przypadku bVec będzie rosnąć w miarę potrzeb, ale wolę pierwsze podejście do być absolutnie pewny, że przydzielanie pamięci odbywa się od razu. Jak wskazuje @Mike Seymour, możesz zadzwonić pod numer bVec.reserve(aList.size()) w drugim przypadku, aby zapewnić jedną alokację.

+0

Niech również sprawi, że Downcast będzie generyczny. Powinien prawdopodobnie nazwać to "static_downcast" również, aby odróżnić od wersji dynamicznej, może się okazać, że będziesz musiał napisać później. –

+0

@Noah Roberts: Dobre punkty. – Troubadour

+0

To powinno być 'resize()', a nie 'reserve()'. Albo to, albo użyj 'back_inserter (bVec)' zamiast 'bVec.begin()', aby wypchnąć obiekty zamiast nadpisywać je. –

1

Można przejść podejście adaptera iteratora, ale proponuję zrobić to poprawnie, jeśli to zrobisz. Albo musisz przesłonić wszystkie rzeczy, które sprawiają, że iterator jest "Iteratorem", albo użyć Boost.Iterator, biblioteki przeznaczonej do ułatwiania takich rzeczy.

Innym rozwiązaniem, które można zastosować, jest stworzenie funktora i użycie std :: transform zamiast std :: copy. Wydaje mi się, że jest to o wiele łatwiejsze podejście. Jeśli używasz kompilatora C++ 0x, możesz nawet użyć lambda.

Edytuj: osoba, która zasugerowała użycie adaptera, wyciągnęła jego odpowiedź, więc pierwszy akapit może nie mieć większego sensu. Używał otoki wokół wektorowych iteratorów, które zwróciły B * zamiast A *, ale pominęło to całą masę pracy, która byłaby konieczna, aby zrobić to poprawnie.

2

Użyj Transformacja:

#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; 
} 
Powiązane problemy