Скажем, у меня есть массив неподписанных символов, который представляет собой пучок объектов POD (например, читается из сокета или через mmap). Какие типы они представляют и в какой позиции определяются во время выполнения, но мы предполагаем, что каждый из них уже правильно выровнен.
Каков наилучший способ "бросить" эти байты в соответствующий тип POD?
Решение должно либо соответствовать стандарту С++ (пусть говорят >= С++ 11), либо, по крайней мере, будет гарантировано работать с g++ >= 4.9, clang++ >= 3.5 и MSVC >= 2015U3. РЕДАКТИРОВАТЬ: В linux, windows, работает под управлением x86/x64 или 32/64-бит.
В идеале я хотел бы сделать что-то вроде этого:
uint8_t buffer[100]; //filled e.g. from network
switch(buffer[0]) {
case 0: process(*reinterpret_cast<Pod1*>(&buffer[4]); break;
case 1: process(*reinterpret_cast<Pod2*>(&buffer[8+buffer[1]*4]); break;
//...
}
или
switch(buffer[0]) {
case 0: {
auto* ptr = new(&buffer[4]) Pod1;
process(*ptr);
}break;
case 1: {
auto* ptr = new(&buffer[8+buffer[1]*4]) Pod2;
process(*ptr);
}break;
//...
}
Оба, похоже, работают, но оба являются действиями AFAIK undefined в С++ 1). И только для полноты: я знаю о "обычном" решении просто скопировать материал в соответствующую локальную переменную:
Pod1 tmp;
std::copy_n(&buffer[4],sizeof(tmp), reinterpret_cast<uint8_t*>(&tmp));
process(tmp);
В некоторых ситуациях это может быть не накладные расходы в других, и в некоторых ситуациях это может быть даже быстрее, но производительность в стороне, я больше не могу, например. измените данные на месте и, если честно, меня просто раздражает, когда я знаю, что у меня есть правильные биты в соответствующем месте в памяти, но я просто не могу их использовать.
Несколько сумасшедшее решение, которое я придумал, следующее:
template<class T>
T* inplace_cast(uint8_t* data) {
//checks omitted for brevity
T tmp;
std::memmove((uint8_t*)&tmp, data, sizeof(tmp));
auto ptr = new(data) T;
std::memmove(ptr, (uint8_t*)&tmp, sizeof(tmp));
return ptr;
}
g++ и clang++, похоже, могут оптимизировать эти копии, но я думаю, что это накладывает большую нагрузку на оптимизатор и может привести к сбою других оптимизаций, не работает с const uint8_t*
(хотя я не хочу на самом деле изменить его) и выглядит просто ужасно (не думайте, что вы получите этот обзор прошлого кода).
1) Первый - это UB, потому что он нарушает строгий псевдоним, второй, вероятно, UB (обсуждается здесь), потому что стандарт просто говорит, что результирующий объект не инициализирован и имеет неопределенное значение (вместо того, чтобы гарантировать, что базовая память не затронута). Я считаю, что первый эквивалентный c-код хорошо определен, поэтому компиляторы могут разрешить это для совместимости с c-заголовками, но я не уверен в этом.