Я уже давно работаю с аппаратными 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
и каково его использование в области сериализации/десериализации? (если есть).
Спасибо за внимание!