С++ Конкатенатные контейнеры полиморфных объектов
Предположим, что у меня есть виртуальный базовый класс и некоторые производные конкретные классы:
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
являются, но, например, но их типы элементов должны быть совместимыми.)