Самый простой способ смешать последовательности типов с iostreams? - PullRequest
0 голосов
/ 24 марта 2010

У меня есть функция void write<typename T>(const T&), которая реализована в терминах записи объекта T в ostream, и функция сопоставления T read<typename T>(), которая читает T из потока istream. Я в основном использую iostreams как формат сериализации в виде простого текста, который, очевидно, прекрасно работает для большинства встроенных типов, хотя я пока не уверен, как эффективно обрабатывать std :: strings.

Я бы тоже хотел написать последовательность объектов, например, void write<typename T>(const std::vector<T>&) или эквивалент на основе итератора (хотя на практике он всегда будет использоваться с вектором). Однако, хотя написать перегрузку, которая перебирает элементы и записывает их, достаточно просто, это не добавляет информации, достаточной для того, чтобы соответствующая операция чтения могла знать, как разделен каждый элемент, что по сути является той же проблемой, что и я. иметь с одной std :: string.

Существует ли единый подход, который может работать для всех основных типов и std :: string? Или, может быть, я могу избежать двух перегрузок, одна для числовых типов, а другая для строк? (Возможно, используя разные разделители или строку, используя механизм экранирования разделителя.)

РЕДАКТИРОВАТЬ: Я ценю часто ощутимую тенденцию, когда сталкиваюсь с такими вопросами, как сказать «вы не хотите делать это» и предлагать лучший подход, но я действительно хотел бы предложения, которые имеют непосредственное отношение к тому, что я спросил, а не к тому, что, как ты считаешь, я должен был спросить вместо этого. :)

Ответы [ 4 ]

1 голос
/ 24 марта 2010

Платформа сериализации общего назначения - hard , а встроенные функции библиотеки iostream на самом деле не соответствуют ей - даже удовлетворительно работать со строками довольно сложно. Я предлагаю вам либо сесть и спроектировать структуру с нуля, игнорируя iostreams (которые затем становятся деталями реализации), либо (более реалистично) использовать существующую библиотеку или, по крайней мере, существующий формат, такой как XML.

0 голосов
/ 26 марта 2010

Вы можете рассмотреть возможность использования boost :: spirit , что упрощает анализ базовых типов из произвольных входных потоков.

0 голосов
/ 24 марта 2010

Если вы хотите избежать экранирования строк, вы можете посмотреть, как работает ASN.1. Это излишне для ваших заявленных требований: строк, основных типов и массивов этих вещей, но принцип заключается в том, что поток содержит однозначную информацию о длине. Поэтому ничего не нужно избегать.

Для очень простого эквивалента вы можете вывести uint32_t как "ui4", за которым следуют 4 байта данных, int8_t как "si1", за которым следует 1 байт данных, IEEE с плавающей точкой как "f4", IEEE удваивается как "f8" и так далее. Используйте некоторый дополнительный модификатор для массивов: «a134ui4», за которым следуют 536 байтов данных. Обратите внимание, что произвольные длины должны быть завершены, тогда как ограниченные длины, такие как число байтов в следующем целом числе, могут быть фиксированного размера (одна из причин, по которой ASN.1 больше, чем вам нужно, заключается в том, что он использует произвольные длины для всего). Строка может быть либо a<len>ui1, либо сокращением, например s<len>:. Читатель действительно очень прост.

Это имеет очевидные недостатки: размер и представление типов должны быть независимыми от платформы, а выходные данные не читаются человеком и не сжимаются.

Вы можете сделать его в основном читабельным для человека, хотя с ASCII вместо двоичного представления арифметических типов (осторожно с массивами: вы можете вычислить длину всего массива перед выводом любого из него, или вы можете использовать разделитель и терминатор, поскольку нет необходимости в экранировании символов), и, при необходимости, добавляя большой жирный видимый человеком разделитель, который десериализатор игнорирует. Например, s16:hello, worlds12:||s12:hello, world значительно легче читать, чем s16:hello, worlds12:s12:hello, world. Просто будьте внимательны, читая, что то, что выглядит как последовательность разделителя, может на самом деле не быть единым, и вам следует избегать попадания в ловушки, например, если предположить, что s5:hello|| в середине кода означает, что строка длиной 5 символов: она может быть частью s15:hello||s5:hello||.

Если у вас нет очень жестких ограничений на размер кода, вероятно, проще использовать универсальный сериализатор с полки, чем писать специализированный. Чтение простого XML с SAX не сложно. Тем не менее, каждый и его собака написали «наконец, сериализатор / парсер / что угодно, что спасет нас от ручного кодирования сериализатора / парсера / что угодно когда-либо еще», с большим или меньшим успехом.

0 голосов
/ 24 марта 2010

По сути, вам придется создать формат файла. Когда вы ограничены встроенными модулями, строками и их последовательностями, вы можете использовать пробелы в качестве разделителей, записывать строки, заключенные в " (экранируя любые " - и затем \ - встречающиеся в потоках сами) и выберите все, что не используется для потоковой передачи встроенных типов в качестве разделителя последовательности. Также может быть полезно сохранить размер последовательности.

Например,

5 1.4 "a string containing \" and \\" { 3 "blah" "blubb" "frgl" } { 2 42 21 }

может быть сериализацией int (5), float (1.4), строки ("a string containing " and \"), последовательности из 3 строк ("blah", "blubb", и "frgl") и последовательность 2 int с (42 и 21).

В качестве альтернативы вы можете сделать, как Нил предлагает в своем комментарии , и рассматривать строки как последовательности символов:

{ 27 'a' ' ' 's' 't' 'r' 'i' 'n' 'g' ' ' 'c' 'o' 'n' 't' 'a' 'i' 'n' 'i' 'n' 'g' ' ' '"' ' ' 'a' 'n' 'd' ' ' '\' }

...