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

71
9

Пытаясь перегруппировать код инфраструктуры, я решил использовать наследование для достижения полиморфного поведения между несколькими классами. Следующий шаг - бросить их в контейнер, перебрать их и DoSomething();

Моя нынешняя конструкция контейнера неуклюжа. Даже если я использую это элегантное решение для контейнера, строительство по-прежнему беспорядок.

vector<unique_ptr<Parent>> vec;

vec.push_back(unique_ptr<Parent>(new ChildA(var1, var2)));
vec.push_back(unique_ptr<Parent>(new ChildB(var3, var4)));
vec.push_back(unique_ptr<Parent>(new ChildC(var5, var6)));

ChildA, ChildB и ChildC все полиморфны друг другу. Var1... var6 могут быть разных типов и необходимы для построения дочерних объектов.

Я хотел бы создать этот вектор в нескольких местах для использования. Проблема в том, что количество детей может меняться и, следовательно, данные, которые дети нуждаются в строительстве. Попытка идти по пути stdarg.h не удалась, поскольку это не поддерживается:

if     !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif

Как бы вы реализовали фабричный метод, который создает этот массив?

UPDATE, желаемое решение:

vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2, var5, var6 typeInfo3);
vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2);

строка 1 создаст тот же массив, что и выше. строка 2 будет использовать один и тот же код для создания аналогичного вектора, но с одним меньшим объектом. Типinfo содержит информацию, необходимую для создания объекта.

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

vec = CreatePolyVec(var1, var2,typeinfo1, var3, var4, typeinfo2, var5);

недостаточно параметров для создания последнего дочернего объекта.

спросил(а) 2013-01-09T15:54:00+04:00 7 лет, 9 месяцев назад
1
Решение
70

С обновлением компилятора в ноябре 2012 года VS 2012 поддерживает вариативные шаблоны, поэтому следующее должно работать (это не совсем тот синтаксис, который вам нужен, но я думаю, что он довольно близок):

struct VectorCreator
{
explicit VectorCreator(std::vector<Parent> &vec)
: vec_(vec)
{}

template <typename Type, typename... ArgType>
VectorCreator& create(ArgType&&... arg)
{
vec_.push_back(std::unique_ptr<Parent>(new Type(std::forward<ArgType>(arg)...));
return *this;
}

private:
std::vector<Parent> &vec_;
};


Используйте следующее:

std::vector<Parent> vec;
VectorCreator(vec).create<ChildA>(var1, var2).create<ChildB>(var3, var4).create<ChildC>(var5, var6);

ответил(а) 2013-01-09T17:37:00+04:00 7 лет, 9 месяцев назад
90

Здесь есть две проблемы: 1. Как определить, какой тип будет иметь каждый ребенок, и 2. Как создать несколько дочерних элементов.

Чтобы решить, какой ребенок создать

Это можно сделать во время компиляции или времени выполнения. Для этого во время компиляции вам нужны шаблоны.

template<class Child, class Arg1, class Arg2>
vector<unique_ptr<Parent>> CreateVec(Arg1&& arg1, Arg2&& arg2)
{
vector<unique_ptr<Parent>> result;
result.push_back(unique_ptr<Child>(
new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
return result;
}

называется следующим CreateVec<MyChild>(myArg1, myArg2).

Если вам нужно решить во время выполнения, вы можете использовать карту заводских функций, индексированных переменной времени выполнения. Или вы можете использовать указатель на заводский объект в качестве переменной времени выполнения.

Чтобы создать несколько дочерних элементов

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

Цепные функции

Это то, что делает iostreams. Создайте свою функцию, которая создает вектор и добавляет одного дочернего объекта, возвращает объект, который позволяет вам добавить второго ребенка, и возвращает себя, позволяя продолжить.

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

class AddChildren
{
vector<unique_ptr<Parent>>& m_vector;
public:
explicit AddChildren(vector<unique_ptr<Parent>>& theVector)
: m_vector(theVector) {}

template<class Child, class Arg1, class Arg2>
AddChildren& add(Arg1&& arg1, Arg2&& arg2)
{
m_vector.push_back(unique_ptr<Child>(
new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
return *this;
}
};

используется следующим образом:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
.add<ChildA>(var1, var2)
.add<ChildB>(var3, var4)
.add<ChildC>(var5, var6);

Если вы используете метод времени выполнения для выбора типа, который вы можете использовать operator() и если он выглядит так:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
(childAType, var1, var2)(childBType, var3, var4)(childCType, var5, var6);

(Это также можно выполнить с помощью выбора типа времени компиляции с использованием фиктивного объекта определенного типа-типа выбора для каждого дочернего типа в качестве параметра.)

Использование вариативных шаблонов

Используйте вариационный шаблон для одновременного удаления трех параметров и добавления дочернего объекта.

void addChildren(vector<unique_ptr<Parent>>& theVector)
{}

template<class FirstChild, class FirstArg1, class FirstArg2, class... Rest>
void addChildren(vector<unique_ptr<Parent>>& theVector,
FirstChild childtype, FirstArg1&& arg1, FirstArg2&& arg2, Rest&&... rest)
{
addChild(theVector, childtype,
std::forward<Arg1>(arg1), std::forward<Arg2>(arg2));
addChildren(theVector, std::forward<Rest>(rest)...);
}

template<class... Args>
vector<unique_ptr<Parent>> CreateVec(Args&&... args)
{
vector<unique_ptr<Parent>> result;
addChildren(result, std::forward<Args>(args)...);
return result;

}

Я предполагаю здесь существование функции addChild которая может добавить addChild учитывая его тип (как параметр) и его аргументы.

Основная проблема заключается в том, что VS2012 не имеет вариационных шаблонов. Существует два способа моделирования вариационных шаблонов. 1. Напишите одну функцию, которая принимает максимальное количество параметров, которые могут вам понадобиться, и по умолчанию большинство из них относятся к известному типу, который вы можете использовать для обозначения "нет". 2. Запишите столько перегрузок, сколько вы считаете нужным.

Если вы знаете, что вам больше не понадобится больше десяти дочерних объектов, второй вариант действительно выполним - вам нужно только написать их один раз и, вероятно, менее 150 строк кода. В качестве альтернативы вы можете использовать Boost.Preprocessor для генерации функций, но это совершенно новый уровень сложности.

ответил(а) 2013-01-09T17:47:00+04:00 7 лет, 9 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема