Стандартный код на С++ для сериализации/десериализации

Я уже давно работаю с аппаратными API-интерфейсами, и почти все API-интерфейсы, которые я работал, имеют интерфейс C. Таким образом, во многих случаях я работал с голыми new s, небезопасной буферизацией и множеством функций C, завернутых кодом С++. В конце концов, граница между чистым кодом C и чистым кодом С++ была испорчена в моем сознании (и я не знаю, может ли пояснить эту границу вообще).

Теперь, из-за каких-то новых требований к стилю кодирования, мне нужно реорганизовать весь код, который предположительно небезопасен, в более безопасный, написанный на С++ (при условии, что код на С++ будет более безопасным) конечной целью является увеличение кода безопасности с помощью инструментов, которые запускает С++.

Итак, чтобы избавиться от всей моей путаницы, я прошу о помощи по нескольким темам C/С++.

memcpy vs std::copy

AFAIK memcpy - это функция, которая лежит на библиотеках C, поэтому это не С++ ish; с другой стороны std::copy - это функция в STL, поэтому он чистый С++.

  • Но это правда? в конце концов, std::copy вызовет std::memcpy (в заголовок cstring), если данные тривиально гибны.
  • Рефакторинг всех вызовов memcpy в вызовы std::copy сделает код более "чистым С++".

Чтобы справиться с новыми требованиями стиля кода, я решил продолжить рефакторинг memcpy, есть некоторые вопросы о memcpy и std::copy:

memcpy является небезопасным, потому что он работает с необработанными указателями void, которые могут управлять любым типом указателя независимо от его типа, но в то же время очень гибкие, std::copy не обладает такой гибкостью, гарантирующей безопасность типа. На первый взгляд, memcpy - лучший выбор для работы с процедурами сериализации и десериализации (это действительно мой реальный случай использования), например, для отправки некоторых значений через пользовательскую библиотеку последовательных портов:

void send(const std::string &value)
{
    const std::string::size_type Size(value.size());
    const std::string::size_type TotalSize(sizeof(Size) + value.size());
    unsigned char *Buffer = new unsigned char[TotalSize];
    unsigned char *Current = Buffer;

    memcpy(Current, &Size, sizeof(Size));
    Current += sizeof(Size);

    memcpy(Current, value.c_str(), Size);

    sendBuffer(Buffer, TotalSize);

    delete []Buffer;
}

Код выше работает отлично, но выглядит ужасно; мы избавляемся от инкапсуляции std::string, использующей ее внутреннюю память с помощью метода std::string::c_str(), мы должны заботиться о распределениях и освобождениях от динамической памяти, играть с указателями и рассматривать все значения как символы без знака (см. следующую часть), вопрос в том, есть ли лучший способ сделать это?

Мои первые попытки решить вышеупомянутые проблемы с помощью std::copy не удовлетворяют меня вообще:

void send(const std::string &value)
{
    const std::string::size_type Size(value.size());
    const std::string::size_type TotalSize(sizeof(Size) + value.size());

    std::vector<unsigned char> Buffer(TotalSize, 0);

    std::copy(&Size, &Size + 1, Buffer.begin());
    std::copy(value.begin(), value.end(), Buffer.begin() + sizeof(Size));

    sendBuffer(Buffer.data(), TotalSize);
}

С помощью вышеописанного подхода управление памятью больше не является проблемой, std::vector берет на себя ответственность за распределение, хранение и окончательное удаление данных в конце области действия, но смешение вызовов std::copy с указателем арифметика и арифметика итераторов довольно раздражает, и в конце концов я игнорирую инкапсуляцию std::vector в вызове sendBuffer.

После предыдущих попыток я закодировал что-то с std::stringstream, но результаты были еще хуже, и теперь мне интересно, если:

  • Существует способ сериализации объектов и значений безопасным способом, без нарушения инкапсуляции, без excesive или запутанной арифметики указателя/итератора и без управления динамической памятью или это просто невозможная цель? (да, я слышал о boost::serialization, но пока мне не разрешено его интегрировать).

и

  • Какое наилучшее использование std::copy для целей сериализации/десериализации? (если есть).
  • Обоснование std::copy ограничено для копирования контейнеров или массивов и использование его для необработанной памяти - плохой выбор?

alloc/free vs new/delete vs std::allocator

Другой большой темой является выделение памяти. AFAIK функции malloc/free не запрещены в область С++, хотя они из C. И операторы new/delete являются из области С++, и они не являются ANSI C.

  • Я прав?
  • new/delete может использоваться в ANSI C?

Предполагая, что мне нужно реорганизовать все C-ароматизированные коды на С++-код, я избавляюсь от всего alloc/free распространенного arround некоторого унаследованного кода, и я обнаружил, что резервирование динамической памяти довольно запутывает, тип void не несет никакой информации о размере, из-за чего невозможно зарезервировать буфер данных с использованием void как type:

void *Buffer = new void[100]; // <-- How many bytes is each 'void'?

Из-за отсутствия указателей с чистым-сырым-двоичным-данным, является обычной практикой создания указателей на unsigned char. char, чтобы совместить количество элементов и их размер. И unsigned, чтобы избежать неожиданных конверсий без подписей во время копирования данных. Может быть, это обычная практика, но это беспорядок... unsigned char не int и float и my_awesome_serialization_struct, если я вынужден выбрать какой-то фиктивный указатель на двоичные данные, я предпочту void * вместо unsigned char *.

Поэтому, когда мне нужен динамический буфер для целей сериализации/десериализации, я не могу избежать материала unsigned char * для реорганизации в управление безопасным буфером типа; но когда я был рэг-рефакторинг всех пар alloc/free в пары new/delete, я читал о std::allocator.

std::allocator позволяет резервировать куски памяти безопасным способом, с первого взгляда я уверен, что это будет полезно, но нет больших различий между распределением с помощью std::allocator<int>::allocate или new int или так я думал, то же было для std::allocator<int>::deallocate и delete int.

И теперь, я потерял север относительно управления динамической памятью, поэтому я спрашиваю:

  • Есть ли хорошая практика на С++, связанная с управлением динамической памятью для целей сериализации/десериализации, которая предоставляет управление безопасным типом?
  • Можно ли избежать использования const char * для буферов памяти сериализации/десериализации?
  • Какое обоснование std::allocator и каково его использование в области сериализации/десериализации? (если есть).

Спасибо за внимание!

Ответ 1

Мой опыт заключается в том, что безопасность типа на С++ означает не только то, что компилятор жалуется на несоответствия типов. Это скорее означает, что вам вообще не нужно заботиться о макете памяти ваших данных. На самом деле, стандарт С++ имеет очень мало требований к макету памяти определенных типов данных.

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