С++ Конкатенатные контейнеры полиморфных объектов

Предположим, что у меня есть виртуальный базовый класс и некоторые производные конкретные классы:

class Base { ... }
class DerivedA : public Base { ... }
class DerivedB : public Base { ... }
class DerivedC : public Base { ... }

И где-то у меня есть векторы объектов каждого производного класса:

std::vector<DerivedA> my_a;
std::vector<DerivedB> my_b;
std::vector<DerivedC> my_c;

Теперь довольно часто мне нужно перебирать все элементы во всех трех векторах и использовать интерфейс базового класса. Я мог бы написать три for-loops, делая то же самое в каждом. Но очевидно, что далеко не оптимальное решение.

Есть ли умный способ объединить векторы в общий контейнер с указателями/ссылочными типами базового класса, так что мне нужно повторять только один раз? Или любая другая идея, как решить эту проблему?

Ответы

Ответ 1

В вашей текущей ситуации нет необходимости в полиморфизме. Вы можете просто использовать вариационный шаблон + функцию более высокого порядка для итерации по векторам. Здесь решение С++ 17 с использованием выражения fold:

template <typename F, typename... Vectors>
void for_all_vectors(F&& f, Vectors&&... vs)
{
    (std::for_each(std::forward<Vectors>(vs).begin(), 
                   std::forward<Vectors>(vs).end(), 
                   f), ...);
}

Использование:

int main()
{
    std::vector<A> my_a;
    std::vector<B> my_b;
    std::vector<C> my_c;

    for_all_vectors([](const auto& x){ something(x); }, my_a, my_b, my_c);
}

живой пример в wandbox


В С++ 11/14 вы можете заменить выражение fold на for_each_argument:

template <typename TF, typename... Ts>
void for_each_argument(TF&& f, Ts&&... xs)
{
    return (void)std::initializer_list<int>{
        (f(std::forward<Ts>(xs)), 0)...};
}

template <typename F, typename... Vectors>
void for_all_vectors(F&& f, Vectors&&... vs)
{
    for_each_argument([&f](auto&& v)
    { 
        std::for_each(v.begin(), v.end(), f);
    }, std::forward<Vectors>(vs)...);
}

живой пример в wandbox

Я объясняю идею этого фрагмента и раскрываю его в этом разговоре CppCon 2015: " for_each_argument объясняется и расширяется > .

Ответ 2

Просто указатель на базовый класс. Вы не можете иметь вектор базы типов и помещать в него производные классы, потому что они могут быть не одного размера, одинаковых функций и т.д.

Так что я бы сделал, это создать вектор или базу типов *, а затем вы можете объединить указатели производного класса.

Вероятно, будет выглядеть примерно так:

vector<base*> v;
v.push_back(&derivedClassVariableA);
v.push_back(&derivedClassVariableB);

Затем, пока функции, которые вы хотите использовать, являются виртуальными в базе и определены в производном, вам должно быть хорошо идти

Ответ 3

Простым решением было бы использовать функцию шаблона, которая выполняет итерацию над элементами вектора и вызывает соответствующую функцию:

class Base {
public:
    virtual int getX() = 0;
};

class Derived1 : public Base {
public:
    int x1=1;
    virtual int getX() { return x1; };
};

class Derived2 : public Base {
public:
    int x2=2;
    virtual int getX() { return x2; };
};

template<typename T>
void callPolymorphic(std::vector<T> &v) {
    for (T& a : v) {
        cout << a.getX() << " ";
    }
}

int main() {

    std::vector<Derived1> my_1(5);
    std::vector<Derived2> my_2(5);

    callPolymorphic(my_1);
    callPolymorphic(my_2);

    return 0;
}

Ответ 4

Я бы просто создал шаблон функции или общую лямбду и назовет ее три раза:

auto my_loop = [](auto& vec){
  for (auto& base : vec) {
      // do something with base...
  }
};
my_loop(my_a);
my_loop(my_b);
my_loop(my_c);

Ответ 5

С другой стороны, вы можете изготовить собственный самодельный адаптер:

#include <iostream>
#include <vector>
#include <functional>
#include <iterator>

struct Base {
    virtual int f() const = 0;
    virtual ~Base() {}
};

struct D1: Base {
    int f() const { return 42; }
};

struct D2: Base {
    int f() const { return 314; }
};

template<typename T, typename... Left, typename... Right>
inline std::vector<T, Left...> operator+(std::vector<T, Left...> &&left, std::vector<T, Right...> &&right) {
    std::vector<T, Left...> retVal(std::move(left));
    using mi = std::move_iterator<typename std::vector<T, Right...>::iterator>;
    retVal.insert(retVal.end(), mi(right.begin()), mi(right.end()));
    return retVal;
}

int main() {
    std::vector<D1> v1(3);
    std::vector<D2> v2(4);
    using View = std::vector<std::reference_wrapper<const Base>>;
    View b(View(v1.cbegin(), v1.cend()) + View(v2.cbegin(), v2.cend()));
    for(Base const &item: b) std::cout << item.f() << std::endl;
}

(Обратите внимание, что базовые просмотренные контейнеры могут быть любыми, vector являются, но, например, но их типы элементов должны быть совместимыми.)