2010-08-18 12 views
10

Ich bin mir ziemlich sicher, dass es keinen Weg gibt, dies explizit zu tun, aber ich würde trotzdem fragen, ob es einen besseren Weg gibt. Ich habe eine Basisklasse A und eine abgeleitete Klasse B, jetzt habe ich eine std :: list von A *, die auf B * 's zeigt, und ich möchte diese Liste von A *' s in einen std :: Vektor von B kopieren * 's so im Grunde möchte ich, dies zu tun:C++ Std :: Kopie mit Typ Umwandlung in abgeleitete Klasse möglich?

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

ich bin ziemlich sicher, dass diese zusammenstellen sollte, wenn die Liste und der Vektor den gleichen Typ sein würde (zB waren beide A * s), aber in dieser seit Fall A * ist die Basisklasse von B * ich kann es auf diese Weise nicht tun, weil ich ausdrücklich, wie dies zum Beispiel zu typisieren haben würde:

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

gibt es eine elegantere Weise als meinen zweiten Ansatz oder werden Ich muss es so machen?

+0

Zu diesem Zweck dynamic_cast ist sicherer als static_cast. static_cast gibt dir ein B *, auch wenn es kein B * ist, während dir dynamic_cast einen Nullzeiger gibt. In beiden Fällen, wenn das A * nicht auf ein B zeigt, erhalten Sie undefiniertes Verhalten, wenn Sie es als B behandeln, aber in der dynamic_cast-Version können Sie zumindest sagen, ob es kein B. –

+2

@David Thornley ist : ... solange 'A' mindestens eine virtuelle Funktion hat. –

+0

@Charles: Danke - guter Fang. –

Antwort

15

Es ist nicht sicher, die Konvertierung implizit, also müssen Sie es explizit machen. Der Standard-Algorithmus eine Art von Transformation zu einer Sequenz, die für die Anwendung ist std::transform, die Sie einen leeren Behälter füllen können wie folgt:

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

Das OP sollte mit diesem IMHO gehen. –

+0

All Caps Identifier sind normalerweise für MACROs reserviert. – Puppy

+0

Ich bin mir nicht sicher, ob das eleganter ist als der Code des OP, aber es ist der STL-Weg :) Ich hasse es herumzusuchen, um herauszufinden, was static_caster macht. – Andrew

7

Definieren Sie einen Funktor, um das Casting z.

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

und verwenden Sie dann std::transform statt std::copy dh

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

Hinweis können Sie auch tun

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

in diesem Fall bVec nach Bedarf wachsen wird, aber ich bevorzuge den ersten Ansatz zu sein absolut sicher, dass die Speicherzuweisung auf einmal erfolgt. Wie @Mike Seymour darauf hinweist, könnte man im zweiten Fall bVec.reserve(aList.size()) anrufen, um eine Zuteilung sicherzustellen.

+0

Kann auch Downcast generisch machen. Sollte man es wahrscheinlich auch "static_downcast" nennen, um von der dynamischen Version zu unterscheiden, kann es sehr gut sein, dass Sie später schreiben müssen. –

+0

@Noah Roberts: Gute Punkte. – Troubadour

+0

Das sollte 'resize()' sein, nicht 'reserve()'. Entweder das, oder verwenden Sie 'back_inserter (bVec)' anstelle von 'bVec.begin()', um die Objekte zu verschieben, anstatt sie zu überschreiben. –

1

Sie könnten den Iterator-Adapter-Ansatz verwenden, aber ich würde vorschlagen, es richtig zu machen, wenn Sie dies tun. Entweder müssen Sie alle Dinge außer Kraft setzen, die einen Iterator zu einem "Iterator" machen, oder Sie verwenden Boost.Iterator, eine Bibliothek, die solche Dinge erleichtern soll.

Der andere Ansatz, den Sie verwenden würden, ist ein Funktor und verwenden Sie std :: transform anstelle von std :: copy. Dies scheint mir ein viel einfacherer Ansatz zu sein. Wenn Sie einen C++ 0x-Compiler verwenden, können Sie sogar einfach ein Lambda verwenden.

Edit: Die Person, die vorgeschlagen, einen Adapter zu verwenden, zog seine Antwort, so dass der erste Absatz nicht viel Sinn machen könnte. Es verwendete einen Wrapper um die Vektoriteratoren, die B * anstelle von A * zurückgaben, aber es ließ eine Menge Arbeit aus, die notwendig wäre, um es richtig zu machen.

2

eine Transformation verwenden:

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