Часто приходится иметь дело с динамическими структурами и обменом ими через сокеты и т.п.
Вообще я максимально стараюсь использовать при обмене только структуры фиксированного размера, а если что-то шибко большое или заранее неизвестного размера, то лучше предусмотреть этот случай вводом дополнительных флагов типа начало/конец и отправить несколько пакетов.
Трудности начинаются, когда всё же необходимо иметь структуры с динамическим размером, а точнее, когда в этой структуре есть несколько элементов с динамическим размером.
Что бы их создавать/парсить обычно объявляется примерно так:
struct MyStruct1 { //-- First dynamic struct for example
double d1;
int fixed1;
char dynamicStr1[]; //-- dynamic data
}
Здесь только один элемент с динамическим размером — dynamicStr[], можно сказать проблем нет, т.к. вполне ясно и понятно что в структуре и можно легко достать.
Когда появляется необходимость во втором, третьем, N, то так как в структуре может быть лишь один элемент с динамическим размером (flexible array member), то в нём придётся хранить все структуры с динамическим размером, а потом, при разборе, доставать.
Примерно так:
stryct MyStructU {
size_t sizeStruct1; //-- Либо можно использовать offset, суть та же
size_t sizeStruct2;
size_t sizeStruct3;
char payloadStruct1Struct2Struct3[0];
}
....
MyStructU *myStructU = (MyStructU *)malloc(sizeof(MyStructU *)+sizeStruct1+sizeStruct2+sizeStruct3);
...
MyStruct3 *myStruct3 = ((uint8_t)myStructU+myStructU.sizeStruct1+myStructU.sizeStruct2);
Мне это не нравится тем, что
- Фактически типы динамических элементов известны лишь косвенно (из комментариев или названий их размеров).
- Для использования элемента динамического типа необходимо каждый раз использовать адресную арифметику.
- Теряется смысл структуры как таковой и превращается в обычный линейный массив.
- Необходимость выведения правильных размеров
Я бы конечно предпочёл как наиболее разумное это написание класса с геттерами/сеттерами для таких структур, НО т.к. попался проект с большим количеством таких мелких структур, то решил попробовать сделать универсальный и удобный в использовании класс.
Требования к нему были такие:
- Не менять исходную структуру, которая используется при обмене.
- Фактически используемые типы должны быть явно объявлены в заголовочных файлах и IDE могла отследить не только их, но и элементы базовой структуры.
- Без необходимости явно указывать размеры динамических структур (только размер их динамических данных)
- Возможность получение линейного последовательного буфера и полного размера, а так же работа из него без копирования.
Так как менять структуру нельзя, а динамические элементы должны быть объявлены явно,
то лучшим решением мне показалось создавать отдельную структуру — «реализацию».
Что бы было явно видно для чего эта структура, то нужно сразу при объявлении унаследоваться от нашего класса, а что бы наш класс знал, какие типы используются придётся его сделать шаблонным.
MyStructUImpl: DynamicStruct<MyStructU, DynamicMember<MyStruct1, &MyStructU::sizeMyStruct1>, DynamicMember<MyStruct2, &MyStructU::sizeMyStruct2>, DynamicMember<MyStruct3, &MyStructU::sizeMyStruct3>> {
MyStruct1 *myStruct1;
MyStruct2 *myStruct2;
MyStruct3 *myStruct3;
}
Такая структура по использованию вырисовывается.
Принцип то в общем прост: должны будем в конструкторе создать линейный массив по размеру динамических структур (т.е. по размеру их самих + размер их динамических данных), и указателям задать адрес где будет начинаться очередная динамическая структура в массиве. Разумеется, саму базовую структуру то же нужно первым делом.
Что бы был доступ к базовой структуре перегрузим оператор указателя.
В итоге для взаимодействий использовать будем только MyStructUImpl, а базовая MyStructU остаётся только для передач по сокетам и т.п.
Полный готовый листинг класса DynamicStruct:
#ifndef DYNAMICSTRUCT_H
#define DYNAMICSTRUCT_H
#include <cstddef>
#include <cstdint>
#include <stdexcept>
template <typename T, typename MemberType>
static size_t offsetof2(MemberType T::*member) { return reinterpret_cast<size_t>(&(reinterpret_cast<T*>(0)->*member)); }
template<typename T, typename M>
static T getClassType(M T::*); // template<auto Ptr> f() { decltype(getClassType(Ptr)); }
#pragma pack(push, 1)
/**
* @brief Класс динамического элемента структуры
* @param Type - Тип элемента
* @param SizePtr - Указатель на элемент, в котором хранится размер этого динамического элемента
*/
template <typename Type, auto SizePtr>
struct DynamicMember {
using type =Type;
static constexpr size_t size =sizeof(Type);
static constexpr decltype(SizePtr) sizePtr =SizePtr;
};
/**
* @brief Сахар для работы с динамическими структурами
* @param Base - Тип базовой структуры
* @param DynamicMembers... - Список динамических элементов в базовой (DynamicMember<Type, &Base::sizeMember>)
* @example
*
* #pragma pack(push, 1)
* struct MyStruct1 { //-- First dynamic struct for example
* int a1;
* char dynamicStr1[]; //-- dynamic data
* }
*
* struct MyStruct2 { //-- Second dynamic struct for example
* double d2;
* int fixed2;
* char dynamicStr2[]; //-- dynamic data
* }
*
* struct MyBase {
* size_t sizeMyStruct1;
* size_t sizeMyStruct2;
* int fixedVal;
* double fixedMore;
* char fixedStr[20];
* uint8_t data[0]; //-- MyStruct1, MyStruct2;
* }
*
* struct MyBaseImp: DynamicStruct<MyBase, DynamicMember<MyStruct1, &MyBase::sizeMyStruct1>, DynamicMember<MyStruct2, &MyBase::sizeMyStruct2>> {
* MyStruct1 *myStruct1; //-- Pointer! Do not initialize!
* MyStruct2 *myStruct2; //-- Pointer! Do not initialize!
* }
* #pragma pack(pop)
*
*
* char *str1 ="DynamicStr1";
* size_t str1Len =strlen(str1);
*
* char *str2 ="Privet,Medved";
* size_t str2Len =strlen(str2);
*
* MyBaseImp myBaseImp={{str1Len, str2Len}};
* myBaseImp->fixedVal =12345;
* myBaseImp->fixedMore =13.30;
* myBaseImp.myStruct1->a1 = 42;
* myBaseImp.myStruct2->d2 = 19.02;
* snprintf(myBaseImp.myStruct1->dynamicStr1, str1Len, str1);
* snprintf(myBaseImp.myStruct2->dynamicStr2, str2Len, str2);
* ...
* socketSend(myBaseImp.buf(), myBaseImp.size());
* ...
* ...
* socketReceive(buf);
* ...
* MyBaseImp myBaseImpRec ={{(uint8_t*)buf}};
* printf(myBaseImpRec.myStruct1->dynamicStr1);
*
* @author 2me@pavelk.ru
*/
template <typename Base, typename ...DynamicMembers>
class DynamicStruct {
public:
/**
* @brief Создаём новую
* @param extraSizeValues - размер дополнительных данных
*/
template<typename ...DynamicMemberDataSizes>
DynamicStruct(DynamicMemberDataSizes... dynamicMemberDataSizes)
{
_bufSize =sizeof(Base)+(sizeof(DynamicMembers)+...)+sum(dynamicMemberDataSizes...);
_buf =new uint8_t[_bufSize];
_bufIsSelf =true;
initDynamicMembers<DynamicMembers...>(0, sizeof(Base), dynamicMemberDataSizes...);
}
/**
* @brief Создаём новую с нулевым размером дополнительных данных
*/
DynamicStruct()
{
_bufSize =(sizeof(DynamicMembers)+...);
_buf =new uint8_t[_bufSize];
_bufIsSelf =true;
initDynamicMembers<DynamicMembers...>(0, sizeof(Base), 0);
}
/**
* @brief Создаём из существующего буфера
* @param data
*/
DynamicStruct(uint8_t *data)
{
if ( data==nullptr ) { throw std::logic_error("Data is nullptr"); }
_bufIsSelf =false;
_buf =data;
const size_t minSize =(sizeof(DynamicMembers)+...);
const size_t typesSize =loadDynamicMembers<DynamicMembers...>(0, sizeof(Base));
//-- Убедимся, что сумма заданных размеров динамических типов не меньше, чем размер самих типов
if ( typesSize<minSize ) { throw std::logic_error("The specified size in data is smaller than the minimum size of the DynamicMembers"); }
_bufSize =sizeof(Base)+typesSize;
}
/**
* @brief
*/
~DynamicStruct()
{
if ( _bufIsSelf && _buf!=nullptr ) { delete []_buf; _buf =nullptr;}
_bufSize =0;
}
/**
* @brief Отдаём основную структуру
* @return
*/
Base *operator->() { return reinterpret_cast<Base*>(_buf); }
/**
* @brief Отдаём полный размер
* @return
*/
size_t size() const { return _bufSize; }
/**
* @brief Отдаём буфер с данными
* @return
*/
uint8_t *buf() const { return _buf; }
private:
uint8_t *_buf =nullptr; //-- Память для хранения как последовательный набор байт
size_t _bufSize =0; //-- Размер буфера
bool _bufIsSelf =true; //-- Буфер создан нами, и освобождаем мы же
/**
* @brief Суммируем значения типов
* @param args
* @return
*/
template<typename ...Args>
size_t sum(Args&&... args) { return (args+...); }
/**
* @brief Инициализируем из нового буфера
* @param idx - Индекс динамического элемента базовой структуры
* @param offset - Смещение в buf на котором закончился предыдущий динамический элемент (его размер+размер дополнительных данных)
* @param dynamicMemberDataSize - Размер динамических данных обрабатываемого динамического элемента
* @param OtherDynamicMemberDataSize - Оставшиеся размеры динамических данных динамического элемента
*/
template <typename TDynamicMember, typename ...OtherDynamicMembers, typename DynamicMemberDataSize, typename ...OtherDynamicMemberDataSize>
void initDynamicMembers(size_t idx, size_t offset, DynamicMemberDataSize dynamicMemberDataSize, OtherDynamicMemberDataSize ...otherDynamicMemberDataSize)
{
//-- Получаем указатель на тип динамического элемент
typename TDynamicMember::type *dynamicMember =reinterpret_cast<decltype(dynamicMember)>(_buf+offset);
//-- Находим итоговый размер динамического элементе
const size_t memberFullSize =TDynamicMember::size+dynamicMemberDataSize;
//-- Устанавливаем указатель на структуру в реализации
*((void **)(reinterpret_cast<uint8_t*>(this+1)+sizeof(void*)*idx)) =dynamicMember;
//-- Устанавливаем размер по указанному адресу, указанному в динамическом элементе
*(&(reinterpret_cast<decltype(getClassType(TDynamicMember::sizePtr))*>(_buf)->*TDynamicMember::sizePtr)) =memberFullSize;
//-- Обрабатываем остальные динамические элементы
if constexpr( sizeof...(OtherDynamicMembers)>0 ) {
if constexpr( sizeof...(otherDynamicMemberDataSize)>0 ) { //-- Если задан размер динамических данных
initDynamicMembers<OtherDynamicMembers...>(idx+1, offset+memberFullSize, otherDynamicMemberDataSize...);
} else { //-- Не задан размер динамических данных, значит считаем, что элемент фиксированного размера
initDynamicMembers<OtherDynamicMembers...>(idx+1, offset+memberFullSize, 0);
}
}
}
/**
* @brief Инициализируем из существующего буфера
* @param idx
* @param offset
* @return Сумма размеров динамических типов
*/
template <typename TDynamicMember, typename ...OtherDynamicMembers>
size_t loadDynamicMembers(size_t idx, size_t offset)
{
//-- Получаем итоговый размер динамического элемента
const size_t typeFullSize =reinterpret_cast<decltype(getClassType(TDynamicMember::sizePtr))*>(_buf)->*TDynamicMember::sizePtr;
//-- Устанавливаем указатель на структуру в нашей
*((void **)(reinterpret_cast<uint8_t*>(this+1)+sizeof(void*)*idx)) =(_buf+offset);
//-- Обрабатываем остальные типы
if constexpr( sizeof...(OtherDynamicMembers)>0 ) {
return typeFullSize +loadDynamicMembers<OtherDynamicMembers...>(idx+1, offset+typeFullSize);
}
return typeFullSize;
}
};
#pragma pack(pop)
#endif
Использовать просто:
MyStructUImpl myStructUImpl={{10, 20, 20}}; //-- Создаём новую и задаём размеры динамических структур
MyStructUImpl myStructUImplFromBuf={{(uint8_t*)buf}}; //-- Создаём из существующего буфера
Вот как-то так =)