Я пишу двоичную версию iostreams. По сути, он позволяет вам записывать двоичные файлы, но дает вам большой контроль над форматом файла. Пример использования:
my_file << binary::u32le << my_int << binary::u16le << my_string;
Записал бы my_int как 32-разрядное целое число без знака, а my_string как строку с префиксом длины (где префикс u16le.) Чтобы прочитать файл обратно, вы должны щелкнуть стрелками. Работает отлично. Тем не менее, я столкнулся с проблемой в дизайне, и я все еще нахожусь на ограждении по этому поводу. Итак, время спросить ТАК. (Мы делаем пару предположений, таких как 8-битные байты, 2-битные дополнения и IEEE с плавающей точкой на данный момент.)
iostreams, под капотом, используйте streambufs. На самом деле это фантастический дизайн - iostreams кодирует сериализацию 'int
' в текст, и позволяет лежащему в основе streambuf обрабатывать все остальное. Таким образом, вы получаете cout, fstreams, stringstream и т. Д. Все они, как iostreams, так и streambufs, шаблонизируются, как правило, на char, но иногда также как wchar. Мои данные, однако, представляют собой поток байтов, который лучше всего представлен как 'unsigned char
'.
Моими первыми попытками было шаблонизировать классы на основе unsigned char
. std::basic_string
шаблонов достаточно хорошо, но streambuf
нет. Я столкнулся с несколькими проблемами с классом с именем codecvt
, который я никогда не мог проследить за темой unsigned char
. Это поднимает два вопроса:
1) Почему стримбуф отвечает за такие вещи? Кажется, что преобразование кода лежит вне ответственности потокового буфера - потоковые буферы должны брать поток и буферизовать данные в / из него. Ничего более. Нечто такое же высокое, как и при преобразовании кода, кажется, что оно должно принадлежать iostreams.
Поскольку я не мог заставить шаблонные потоковые буферы работать с неподписанным символом, я вернулся к типу char и просто преобразовал данные между типом char / unsigned. Я пытался свести к минимуму количество приведений по понятным причинам. Большая часть данных в основном оказывается в функции read () или write (), которая затем вызывает основной поток потоков. (И используйте приведение в процессе.) Функция чтения в основном:
size_t read(unsigned char *buffer, size_t size)
{
size_t ret;
ret = stream()->sgetn(reinterpret_cast<char *>(buffer), size);
// deal with ret for return size, eof, errors, etc.
...
}
Хорошее решение, плохое решение?
Первые два вопроса указывают на необходимость дополнительной информации. Во-первых, рассматривались такие проекты, как boost :: serialization, но они существуют на более высоком уровне, поскольку они определяют свой собственный двоичный формат. Это больше для чтения / записи на более низком уровне, где требуется определить формат, или формат уже определен, или объемные метаданные не требуются или не желательны.
Во-вторых, некоторые спрашивают о модификаторе binary::u32le
. Это экземпляр класса, который содержит желаемую последовательность и ширину, на данный момент, возможно, подпись в будущем. Поток содержит копию последнего переданного экземпляра этого класса и использовал его в сериализации. Это был обходной путь, я изначально попытался перегрузить оператор << таким образом: </p>
bostream &operator << (uint8_t n);
bostream &operator << (uint16_t n);
bostream &operator << (uint32_t n);
bostream &operator << (uint64_t n);
Однако в то время это, похоже, не работало. У меня было несколько проблем с неоднозначным вызовом функции. Это было особенно верно в отношении констант, хотя, как предлагал один из них, вы могли разыграть или просто объявить его как const <type>
. Кажется, я помню, что была еще одна большая проблема.