2013-08-10 3 views
2

Этот код пытается использовать copy_if() на векторе полиморфных указателей:Как использовать copy_if() полиморфно?

#include <iostream> 
#include <algorithm> 
#include <vector> 

using namespace std; 

class AbstractBase 
{ 
    public: 
     virtual bool IsDerived1() const { return false; } 
     virtual void Print() const = 0; 
}; 

class Derived1 : public AbstractBase 
{ 
    public: 
     virtual bool IsDerived1() const { return true; } 
     virtual void Print() const { cout << "Derived1" << endl; } 
}; 

class Derived2 : public AbstractBase 
{ 
    public: 
     virtual void Print() const { cout << "Derived2" << endl; } 
}; 

// This function returns the elements of v that are of type Derived1. 
vector<Derived1*> SelectDerived1(const vector<AbstractBase*>& v) 
{ 
    vector<Derived1*> derived1s; 

#define USE_COPY_IF 0 

#if USE_COPY_IF 
    // attempt to use copy_if - does not compile: 
    //  /usr/include/c++/4.7/bits/stl_algo.h:990:6: 
    //  error: invalid conversion from 'AbstractBase*' to 'Derived1*' 
    //  [-fpermissive] 
    copy_if(v.begin(), v.end(), derived1s.begin(), 
      [](AbstractBase* elem){ return elem->IsDerived1(); }); 
#else 
    for (auto it = v.begin(); it != v.end(); ++it) 
     if ((*it)->IsDerived1()) 
      derived1s.push_back(static_cast<Derived1*>(*it)); 
#endif 

    return derived1s; 
} 

int main() 
{ 
    vector<AbstractBase*> v; 
    Derived1* d1 = new Derived1; 
    Derived2* d2 = new Derived2; 
    v.push_back(d1); 
    v.push_back(d2); 

    vector<Derived1*> derived1s = SelectDerived1(v); 
    for (auto it = derived1s.begin(); it != derived1s.end(); ++it) 
     (*it)->Print(); 

    delete d1; 
    delete d2; 

    return 0; 
} 

код компилируется и работает отлично с USE_COPY_IF установлен в 0:

$ g++ -std=c++11 test_copy_if.cc 
$ ./a.out 
Derived1 

Но мне не удалось получить он работает с copy_if() - см. сообщение об ошибке в комментарии.

Не существует способа?

+2

Вы знаете, что это не безопасно для типов вообще, даже принимая во внимание учетную запись 'elem-> IsDerived1()'? (Любой может создать новый подкласс, который не является подтипом 'Derived1', но возвращает' true' из 'IsDerived1'.) – delnan

+0

Это' static_cast' вы делаете в ручном копировании, это не делается с помощью 'std :: copy', поэтому он пытается скопировать из коллекции, содержащей один тип, в коллекцию совершенно другого типа, который, конечно, не будет работать. –

+0

Вы можете комбинировать 'copy_if' (с' dynamic_cast' и другим вектором 'AbstractBase *') и 'std :: transform' для кастинга. –

ответ

4

Вы можете определить функцию transform_if (которая отсутствует в стандарте):

template <class InIt, class OutIt, class Pred, class Trafo> 
OutIt transform_if (
    InIt begin_in, InIt end_in, 
    OutIt begin_out, 
    Pred predicate, 
    Trafo trafo 
) { 
    OutIt itout = begin_out; 
    for (InIt itin = begin_in; itin != end_in; ++itin) { 
     if (predicate (*itin)) { 
      (*itout) = trafo (*itin); 
      ++itout; 
     } 
    } 
} 

Тогда вы можете написать:

transform_if(v.begin(), v.end(), derived1s.begin(), 
     [](AbstractBase* elem){ return elem->IsDerived1(); }, 
     [](AbstractBase* elem){ return static_cast<Derived1*> (elem); } 
); 

или определить transform_and_keep_if делает чек после преобразования :

template <class InIt, class OutIt, class Trafo, class Pred> 
OutIt transform_and_keep_if (
    InIt begin_in, InIt end_in, 
    OutIt begin_out, 
    Trafo trafo, 
    Pred predicate 
) { 
    OutIt itout = begin_out; 
    for (InIt itin = begin_in; itin != end_in; ++itin) { 
     auto transformed = trafo (*itin); 
     if (predicate (transformed)) { 
      (*itout) = transformed; // or std::move (transformed) 
      ++itout; 
     } 
    } 
} 

а затем написать:

transform_and_keep_if(v.begin(), v.end(), derived1s.begin(), 
     [](AbstractBase* elem){ return dynamic_cast<Derived1*> (elem); }, 
     [](Derived1* elem){ return elem != NULL; }, 
); 
+0

Ужасно это не в стандарте, но в любом случае спасибо. – user1387866

+1

Какая польза от использования 'std :: function' здесь? – jrok

+0

Это более гибко, поскольку вы можете передать любой объект, подобный функции. Вам понадобится std :: function, как только вы поймаете локальные переменные внутри '[]' функции лямбда, так как тогда лямбда будет объектом. – JohnB

0

Вы можете разметить v за один проход с std::partition поставить все Derived1 указатели в начале вашего вектора, а затем вызвать std::transform фактически сделать копирование. Код будет выглядеть следующим образом:

auto deriveds_end = std::partition(v.begin(), v.end(), 
             [](AbstractBase* e){ 
              return e->IsDerived1(); 
             }); 
    std::transform(v.begin(), deriveds_end, 
        std::back_inserter(derived1s), 
        [](AbstractBase* e){ 
         return static_cast<Derived1*>(e); 
        }); 

Единственная загвоздка в том, что станд :: раздел изменяет последовательность это разделение, которое означает, что вы не можете передать v по константной ссылке, вы можете передать его копии или вместо константы (если вы не возражаете, v переустановка при вызове функции).

Однако если бы это было, я бы просто придерживаться цикла:

for(auto i : v) 
     if(i->IsDerived1()) 
      derived1s.push_back(static_cast<Derived1*>(i); 

Он не получает гораздо более емким, чем это.

Смежные вопросы