С++ std:: copy с типом, отличным от производного класса?

Я уверен, что это не так, но я бы хотел попросить, если есть лучший способ. У меня есть базовый класс A и производный класс B, теперь у меня есть std:: список A *, который указывает на B *, и я хочу скопировать этот список A * в std::vector B *, поэтому в основном я хочу для этого:

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

Я уверен, что это должно компилироваться, когда список и вектор будут одного и того же типа (например, оба были A *), но так как в этом случае A * является базовым классом B *, я не могу сделать это так, потому что мне пришлось бы явно приводить пример, например:

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

Есть ли более элегантный способ, чем мой второй подход, или мне нужно сделать это так?

Ответ 1

Небезопасно делать преобразование неявно, поэтому вы должны сделать его явным. Стандартный алгоритм применения какого-либо преобразования в последовательность - это std::transform, который можно использовать для заполнения пустого контейнера следующим образом:

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

Ответ 2

Определите функтор для выполнения каста, например.

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

а затем используйте std::transform вместо std::copy i.e.

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

Заметьте, вы также можете сделать

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

и в этом случае bVec будет расти по мере необходимости, но я предпочел бы, чтобы первый подход был абсолютно уверен, что выделение памяти выполняется сразу. Как отмечает @Mike Seymour, вы можете назвать bVec.reserve( aList.size() ) во втором случае, чтобы обеспечить одно выделение.

Ответ 3

Используйте преобразование:

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

Ответ 4

Вы можете использовать подход адаптера итератора, но я предлагаю сделать это правильно, если вы это сделаете. Либо вам нужно переопределить все, что делает итератор "Итератором", либо использовать Boost.Iterator, библиотеку, предназначенную для облегчения таких вещей.

Другой подход, который вы используете, - это сделать функтор и использовать std:: transform вместо std:: copy. Это показалось бы мне гораздо более легким подходом. Если вы используете компилятор С++ 0x, вы можете просто использовать лямбда.

Изменить: человек, который предложил использовать адаптер, вытащил свой ответ, поэтому первый абзац может не иметь большого смысла. Он использовал обертку вокруг векторных итераторов, которые вернули B * вместо A *, но он оставил много работы, которая необходима для правильной работы.