Как реализовать вектор C ++, который указывает на другие, многократно типизированные векторы?

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

Я могу добиться этого (в определенной степени) следующим образом:

//header
enum TypeID { TypeA_ID, TypeA_ID, TypeA_ID, TypeIDAmount };

vector<TypeA> vectorA;
vector<TypeB> vectorB;
vector<TypeC> vectorC;

//cpp
TypeBase* LookUp(TypeID type, int index)
{
switch(type)
{
case TypeA_ID: return (TypeBase*) &vectorA[index];
case TypeB_ID: return (TypeBase*) &vectorB[index];
case TypeC_ID: return (TypeBase*) &vectorC[index];
}
}

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

Более удобная для компиляции (но более уродливая) опция, я думал, что-то вроде этого

//header
void* vectorArray;

//cpp
void Initialize()
{
vectorArray = new void*[TypeIDAmount];
vectorArray[0] = new vector<TypeA>;
vectorArray[1] = new vector<TypeB>;
vectorArray[2] = new vector<TypeC>;
}

TypeBase* LookUp(TypeID type, int index)
{
void* pTypedVector = &vectorArray[type];
switch(type)
{
case TypeA_ID: return (TypeBase*) (*(vector<TypeA>*)pTypedVector)[index];
case TypeB_ID: return (TypeBase*) (*(vector<TypeB>*)pTypedVector)[index];
case TypeC_ID: return (TypeBase*) (*(vector<TypeC>*)pTypedVector)[index];
}
}

(ЭВ!)

Есть ли что-нибудь, что будет работать в общем, как это?

vector< vector<?>* > vectorOfVariedVectors;

Редактировать:

Мотивация для этой структуры — хранить компоненты в шаблоне проектирования Entity-Component.

Причина, по которой я хочу, чтобы типы (или, скорее, компоненты) были смежными, заключается в том, чтобы иметь возможность проходить их поперек кеш-памяти. Это означает, что я хочу, чтобы сами экземпляры были смежными. Хотя использование смежных указателей дало бы мне поведение, подобное тому, что я хочу, если они указывают на «случайные» места в памяти, пропадание кэша все равно будет происходить при извлечении их данных.

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

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

Решение найдено

Спасибо Дмитрию Леденцову за указание на Эта статья. Это в значительной степени то, что я искал.

0

Решение

Как уже отмечали другие в комментариях, вероятно, есть лучшее решение вашей проблемы в большем масштабе, чем тот контейнер, который вы ищете. Во всяком случае, вот как вы могли бы сделать то, что вы просите.

Основная идея заключается в хранении std::vectorс std::unique_ptrс BaseTypeв std::map с std::type_indexкак ключи. В примере используются функции C ++ 11. Обработка ошибок во время выполнения опущена для краткости.

Во-первых, некоторые заголовки:

#include <cstddef>      // std::size_t
#include <iostream>     // std::cout, std::endl
#include <map>          // std::map
#include <memory>       // std::unique_ptr
#include <sstream>      // std::ostringstream
#include <string>       // std::string
#include <type_traits>  // std::enable_if, std::is_base_of
#include <typeindex>    // std::type_index
#include <typeinfo>     // typid, std::type_info
#include <utility>      // std::move
#include <vector>       // std::vector

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

class BaseType
{

public:

virtual ~BaseType() noexcept = default;

virtual std::string
name() const = 0;
};

template<char C>
class Type : public BaseType
{

private:

const std::string name_;

public:

Type(const std::string& name) : name_ {name}
{
}

virtual std::string
name() const final override
{
std::ostringstream oss {};
oss << "Type" << C << "(" << this->name_ << ") @" << this;
return oss.str();
}
};

Теперь к фактическому контейнеру.

class PolyContainer final
{

private:

std::map<std::type_index, std::vector<std::unique_ptr<BaseType>>> items_ {};

public:

void
insert(std::unique_ptr<BaseType>&& item_uptr)
{
const std::type_index key {typeid(*item_uptr.get())};
this->items_[key].push_back(std::move(item_uptr));
}

template<typename T,
typename = typename std::enable_if<std::is_base_of<BaseType, T>::value>::type>
BaseType&
lookup(const std::size_t i)
{
const std::type_index key {typeid(T)};
return *this->items_[key].at(i).get();
}
};

Обратите внимание, что мы даже не объявили возможные подтипы BaseType до сих пор. То есть, PolyContainer не нужно изменять каким-либо образом, если добавлен новый подтип.

я сделал lookup шаблонная функция, потому что я нахожу ее намного чище. Если вы не хотите делать это, вы можете добавить std::type_info параметр, а затем использовать lookup(typid(SubType), 42) вместо lookup<SubType>(42),

Наконец, давайте использовать то, что у нас есть.

using TypeA = Type<'A'>;
using TypeB = Type<'B'>;
using TypeC = Type<'C'>;
// As many more as you like...

int
main()
{
PolyContainer pc {};
pc.insert(std::unique_ptr<BaseType> {new TypeA {"first"}});
pc.insert(std::unique_ptr<BaseType> {new TypeA {"second"}});
pc.insert(std::unique_ptr<BaseType> {new TypeB {"third"}});
pc.insert(std::unique_ptr<BaseType> {new TypeC {"fourth"}});
pc.insert(std::unique_ptr<BaseType> {new TypeB {"fifth"}});
std::cout << pc.lookup<TypeB>(0).name() << std::endl;
std::cout << pc.lookup<TypeB>(1).name() << std::endl;
return 0;
}
1

Другие решения


По вопросам рекламы [email protected]