2009-07-24 8 views
8

Mam klasę bazową z kilkoma klasami rozszerzającymi. Mam ogólne narzędzia biblioteki, które tworzą wektor zawierający wskaźniki do klasy bazowej, tak aby każda z podklas działała. Jak mogę rzucić wszystkie elementy wektora do określonej klasy potomnej?Czy mogę rzucić std :: vector <Animal*> na std :: vector <Dog*> bez patrzenia na każdy element?

// A method is called that assumes that a vector containing 
// Dogs casted to Animal is passed. 
void myDogCallback(vector<Animal*> &animals) { 
    // I want to cast all of the elements of animals to 
    // be dogs. 
    vector<Dog*> dogs = castAsDogs(animals); 
} 

Moje naiwne rozwiązanie mogłoby wyglądać tak:

// A method is called that assumes that a vector containing 
// Dogs casted to Animal is passed. 
void myDogCallback(vector<Animal*> &animals) { 
    // I want to cast all of the elements of animals to 
    // be dogs. 
    vector<Dog*> dogs; 
    vector<Animal*>::iterator iter; 
    for (iter = animals.begin(); iter != animals.end(); ++iter) { 
     dogs.push_back(dynamic_cast<Dog*>(*iter)); 
    } 
} 
+1

Duplikat: http://stackoverflow.com/questions/902667/stl-container-assignment- i-const-pointers – GManNickG

+4

Nie jest do końca dupe - zauważ, że nie kopiuje z 'wektora ' do 'wektora ', ale na odwrót! –

+0

Chyba szuka automatycznej/ukrytej przygnębienia! – Abhay

Odpowiedz

0

Kod jak napisane wprowadzi kilka zerowych wskaźników do swojej wektora psów gdy wektor zwierzęta zawiera inne specjalizacje zwierzęcych.

vector<Dog*> dogs; 
vector<Animal*>::iterator iter; 
Dog* dog; 

for(iter = animals.begin(); iter != animals.end(); ++iter) 
{ 
    dog = dynamic_cast<Dog*>(*iter); 
    if(dog) 
    { 
    dogs.push_back(dog); 
    } 
} 
0

Zwykle nie jest bardzo dobrze używać dynamic_cast do downcastingu. Prawdopodobnie powinieneś refaktoryzować swój kod, aby nie trzeba było używać wyraźnego downcastingu.

Aby uzyskać więcej informacji, zobacz numer CPP FAQ lite.

UPD również zobaczyć Stroustrup page (poszukiwania „Dlaczego nie mogę przypisać wektor z wektorem?”)

10

Można użyć std::transform. Nadal używa for() wewnętrznie, ale dostaniesz realizację dwóch ciągów:

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

struct Animal { virtual ~Animal() {} }; 
struct Dog : Animal { virtual ~Dog() {} }; 

template<typename Target> 
struct Animal2Target { Target* operator()(Animal* value) const { return dynamic_cast<Target*>(value); } }; 

void myDogCallback(vector<Animal*> &animals) { 
{ 
    vector<Dog*> dogs; 
    transform(animals.begin(), animals.end(), dogs.begin(), Animal2Target<Dog>()); 
}
+2

To wciąż "patrzy na każdy element", przepływ jest po prostu inaczej ułożony. –

0

Istnieją dwie opcje. Najprostszym jest użycie czegoś takiego jak remove_copy_if. Nie potrafię wyjaśnić, dlaczego tak to nazywają, ale kopiuje elementy z jednego kontenera do drugiego, aby spełnić predykat. Oto podstawowa idea (niesprawdzone):

struct IsDog : unary_function < Animal *, bool > { 
    bool operator()(Animal * animal) const { 
    return dynamic_cast <Dog*> (animal); 
    } 
}; 

void foo (vector<Animal*> animals) { 
    vector<Dog*> dogs; 
    std::remove_copy_if (animals.begin() 
    , animals.end() 
    , back_inserter (dogs) 
    , std::not1 (IsDog())); // not1 here negates the result of IsDog! 


    // dogs now contains only animals that were dogs 

}

Przypuszczam sposób spojrzeć na remove_copy_if jest myśleć o nim jako copy_unless.

Alternatywnym podejściem, jeśli opierasz swój kod tylko wokół iteratorów, jest owinięcie iteratora dla wektora < Zwierzę *> z takim, które zwraca tylko psy z kolekcji. Główną zaletą jest to, że nadal masz tylko jeden kontener, ale oczywiście zapłacisz trochę więcej, ponieważ algorytm będzie nawigował po całej kolekcji zwierząt.

class dog_iterator // derive from std::iterator probably with bidirectinoal tag 
{ 
private: 
    vector<Animals*>::iterator getNextDogIter (vector<Animals*>::iterator iter) { 
    while (iter != m_end) { 
     if (0 != dynamic_cast<Dog*> (*iter)) { 
     break; 
     } 
     ++iter; 
    } 
    return iter; 
    } 

public: 
    dog_iterator (vector<Animals*>::iterator iter, vector<Animals*>::iterator end) 
    : m_end (end) 
    , m_iter (getNextDogIter (iter)) 
    { 
    } 

    // ... all of the usual iterator functions 

    dog_iterator & operator++() 
    { 
    // check if m_iter already is at end - otherwise: 
    m_iter = getNextDogIter (m_iter + 1); 
    return *this; 
    } 
    // ... 
}; 

To bardzo trudne, ale mam nadzieję, że pokazuje wam podstawową zasadę.

0

Jeśli mówią, że można zagwarantować, że każdy element jest naprawdę pies wtedy właśnie static_cast tj

void myDogCallback(vector<Animal*> &animals) { 

    const vector<Animal*>::size_type numAnimals = animals.size(); 

    vector<Dog*> dogs; 
    dogs.reserve(numAnimals); 

    for (vector<Animal*>::size_type i = 0; i < numAnimals; ++i) { 
     dogs.push_back(static_cast<Dog*>(animals[i])); 
    } 
} 

zwykle zawsze uzyskać odruchowej reakcji od ludzi, że to jest złe i należy zawsze używać dynamic_cast, ale w rzeczywistości, jeśli możesz udzielić gwarancji na temat typu, to jest to całkowicie bezpieczne, a IMO to rozsądna rzecz.

Ponadto gwarancja oznacza, że ​​nowy wektor ma taki sam rozmiar, dlatego należy zarezerwować to samo miejsce, aby uniknąć jakichkolwiek przydziałów w każdym push_back.Jako alternatywny pętli Użyłem indeksu tylko dlatego, że zawsze myślę iteracji przy użyciu indeksu musi być szybsza niż iteratora ale to chyba bzdura :)

0

Kiedy można zapewnić, że Twój std::vector<Animal*> zawiera tylko Dog* można użyć reinterpret_cast .

0

Mieszanie metodologię std::transform z static_cast (bo jesteś pewien swojego bezpieczeństwa) może wyglądać tak:

std::transform(animals.begin(), animals.end(), 
       std::back_insert_iterator<std::vector<Dog*>>(dogs), 
       [](auto ptr) { return static_cast<Dog*>(ptr); });