Упрощение работы с динамическими структурами в C++

Динамическая структура

Часто приходится иметь дело с динамическими структурами и обменом ими через сокеты и т.п.

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

Трудности начинаются, когда всё же необходимо иметь структуры с динамическим размером, а точнее, когда в этой структуре есть несколько элементов с динамическим размером.
Что бы их создавать/парсить обычно объявляется примерно так:

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}}; //-- Создаём из существующего буфера

Вот как-то так =)

Related posts

QML Сделать задержку перед началом анимации

QtCreator не открывает диалог выбора файлов, проектов, не открывает проект

Сборка Qt 6.8 (Dev) из исходников в Docker контейнере (Linux)