Есть ли контейнер STL, который хранит массив элементов в непрерывной памяти, где размер элемента указан во время выполнения?
Я пытаюсь создать контейнер, который выглядит близко к тому, как работает моя спецификация файла. Это как вектор, но тип элементов определяется хеш-таблицей.
Если бы я знал тип во время компиляции, я мог бы просто написать что-то вроде этого:
struct foo {
float a,b,c;
int d;
byte e,f;
};
std::vector<foo> foovector;
foovector.push_back(foo f);
У меня нет структуры во время компиляции. У меня есть только схема, которую я получаю из заголовка файла. Все элементы имеют одинаковый размер и имеют одинаковые смещения для каждого элемента внутри элемента. Контейнер имеет хеш-таблицу, определенную до добавления каких-либо элементов.
typedef Toffset uint; //byte offset;
typedef Ttype uint; //enum of types
std::unordered_map<std::string, std::pair<Toffset,Ttype>> itemKey;
itemKey["a"] = 0;
itemKey["b"] = 4;
itemKey["c"] = 8;
itemKey["d"] = 12;
itemKey["e"] = 16;
itemKey["f"] = 17;
nstd::interleaved_vector superfoo(itemKey, 10); //hashtable, pre-allocation size
nstd::interleaved_vector::iterator myIterator;
myIteratorGlobal = superfoo.begin;
myIteratorA = superfoo["a"].begin;
myIteratorB = superfoo["b"].begin;
*myIteratorB = 2.0f;
*myIteratorGlobal["d"] = 512;
Идея в том, что я могу быстро и легко извлекать необработанные данные из файлов. Смещать итераторы легко. Мои вопросы:
Что-нибудь уже делает это?
Это плохая идея? Должен ли я просто создать вектор и обновить каждый элемент? Я ожидаю иметь миллионы элементов. Диапазон размеров foo будет от 20 до 200 байт.
Это плохая идея? Вместо этого я должен создать связанные векторы, по одному для каждого элемента?
Или этот "interleaved_vector" является хорошим решением моей проблемы?
3 ответа
Учитывая этот код:
typedef Toffset uint; //byte offset;
typedef Ttype uint; //enum of types
typedef std::pair<Toffset,Ttype> member;
typedef std::unordered_map<std::string, member> memberdefs;
memberdefs itemKey;
itemKey["a"] = member(0, 0);
itemKey["b"] = member(4, 1);
itemKey["c"] = member(8, 2);
itemKey["d"] = member(12,1);
itemKey["e"] = member(16,3);
itemKey["f"] = member(17,2);
Вы можете читать в буфер char* и использовать простой класс-оболочку. Все еще подвержен ошибкам и сильно сбивает с толку. Эта демонстрация не имеет итератора (хотя это было бы просто) и требует, чтобы внешний буфер оставался в области действия как минимум до тех пор, пока класс делает это.
class interleaved_vector {
const char* buffer;
size_t count;
size_t size;
std::shared_ptr<memberdefs> members;
public:
class dynamic_object {
const char* buffer;
std::shared_ptr<memberdefs> members;
friend interleaved_vector;
dynamic_object(const char* buffer_, std::shared_ptr<memberdefs> members_)
:buffer(buffer_), members(members_)
{}
dynamic_object& operator=(const dynamic_object& b) = delete;
public:
dynamic_object(const dynamic_object& b)
:buffer(b.buffer), members(b.members)
{}
template <class T>
T get(const std::string& member) const {
assert((*members)[member].second > 0); //no new members, requires pos sizes
assert((*members)[member].second == sizeof(T));
return *reinterpret_cast<T*>(buffer+(*members)[member].first); //technically undefined I think
};
template <>
T* get<T*>(const std::string& member) const {
assert((*members)[member].second > 0); //no new members, requires pos sizes
assert((*members)[member].second == sizeof(T));
return reinterpret_cast<T*>(buffer+(*members)[member].first); //technically undefined I think
};
void* operator[](const std::string& member) const {
assert((*members)[member].second > 0); //no new members, requires pos sizes
assert((*members)[member].second == sizeof(T));
return reinterpret_cast<void*>(buffer+(*members)[member].first); //technically undefined I think
};
};
interleaved_vector(const char* buffer_, size_t count_, size_t size_, const memberdefs& members_)
:buffer(buffer_), count(count_), size(size_), members(members_)
{}
dynamic_object get(size_t index) const {
assert(index<count);
return dynamic_object(buffer + size*index, members);
}
dynamic_object operator[](size_t index) const {
assert(index<count);
return dynamic_object(buffer + size*index, members);
}
size_t size() {
return count;
}
};
Это позволило бы такой код:
size_t element_size = 32;
size_t num_elements = 1000000
char * buffer = new char[num_elements*element_size];
/*read into buffer*/
interleaved_vector iv(buffer, num_elements, element_size , members);
/*interleaved_vector DOES NOT COPY BUFFER. BUFFER MUST REMAIN IN SCOPE*/
for(int i=0; i<iv.size(); ++i) {
for(memberdefs::const_iterator j=members.begin(); j!=members.end(); ++j) {
dynamic_object ob = iv[i];
std::cout << "data[" << i << "]." << j.first << " = ";
std::cout << ob.get<int>(j.first) << '\n';
}
}
Этот демонстрационный код предполагает, что все участники являются целыми числами (get), но, надеюсь, вы сможете увидеть, что предназначено.
Есть ли контейнер STL, который хранит массив элементов в непрерывной памяти, где размер элемента указан во время выполнения?
Нет.
То, что вы просите, выглядит как конкретная реализация пула памяти. Может быть, вам пригодится библиотека Boost.Pool или другие реализации? Написание собственного не должно быть трудным, если вы привыкли работать с необработанной памятью и C++- специфическим созданием / уничтожением объектов.
Чтобы ответить на ваши вопросы:
Что-нибудь уже делает это?
Для меня идея выглядит как пул памяти. Существует множество типов пулов памяти, поэтому реализация, которую вы захотите, полностью зависит от ваших конкретных потребностей.
Это плохая идея? Должен ли я просто создать вектор и обновить каждый элемент? Я ожидаю иметь миллионы элементов. Диапазон размеров foo будет от 20 до 200 байт.
Неплохая идея, если вы хотите ограничить фрагментацию памяти. Пулы часто используются для исправления этой и других проблем, связанных с организацией памяти.
Например, это часто случается в видеоиграх, в основном на консоли, но также и на ПК, если вам нужна высокая производительность или много памяти.
Я просто не рекомендовал бы беспокоиться, если вы делаете прототип или если у вас нет тонны данных для размещения. Если вы это сделаете, то, возможно, сначала было бы неплохо реализовать простейшее распределение (с векторами и новым), скрытое за фабрикой, и это позволило бы вам заменить реализацию фабрики с помощью пула. Таким образом, вы сначала проверяете, что все работает, а затем оптимизируете.
Вы могли бы написать свой собственный класс, но это было бы серьезной болью. Лучше просто использовать vector (или boost::ptr_vector), который не требует усилий с вашей стороны и легко читается каждым программистом, который придет.