Использование переинтерпретации приведения для сохранения структуры или класса в файл - PullRequest
4 голосов
/ 09 декабря 2010

Это то, что профессор показал нам в своих сценариях. Я не использовал этот метод ни в одном из написанных мной кодов.

По сути, мы берем класс или структуру и повторно интерпретируем ее и сохраняем всю структуру следующим образом:

struct Account
{
    Account()
    {   }
    Account(std::string one, std::string two)
        : login_(one), pass_(two)
    {   }

private:
    std::string login_;
    std::string pass_;
};

int main()
{
    Account *acc = new Account("Christian", "abc123");

    std::ofstream out("File.txt", std::ios::binary);
    out.write(reinterpret_cast<char*>(acc), sizeof(Account));
    out.close();

Это производит вывод (в файле)

ÍÍÍÍChristian ÍÍÍÍÍÍ              ÍÍÍÍabc123 ÍÍÍÍÍÍÍÍÍ     

Я в замешательстве. Работает ли этот метод на самом деле, или он вызывает UB, потому что магические вещи происходят внутри классов и структур, которые являются прихотями отдельных компиляторов?

Ответы [ 7 ]

10 голосов
/ 09 декабря 2010

На самом деле это не работает, но также не вызывает неопределенного поведения.

В C ++ допустимо переосмыслить любой объект как массив char, поэтому здесь нет неопределенного поведения.

Однако результаты, как правило, можно использовать только в том случае, если класс является POD (фактически, если класс представляет собой простую структуру в стиле C) и является автономным (то есть структура не имеет данных указателя)члены).

Здесь Account - это не POD, поскольку в нем std::string членов.Внутренние элементы std::string определяются реализацией, но это не POD, и в нем обычно есть указатели, которые ссылаются на некоторый выделенный в куче блок, где хранится фактическая строка (в вашем конкретном примере реализация использует оптимизацию с небольшими строками)где значение строки хранится в самом объекте std::string).

Есть несколько проблем:

  • Вы не всегда получите ожидаемые результаты.Если бы у вас была более длинная строка, std::string использовал бы буфер, выделенный в куче, для хранения строки, и в итоге вы просто сериализовали бы указатель, а не указанную строку.

  • Вы не можете использовать данные, которые вы здесь сериализовали.Вы не можете просто интерпретировать данные как Account и ожидать, что они будут работать, потому что конструкторы std::string не будут вызываться.

Короче говоря, вы не можете использовать этот подходдля сериализации сложных структур данных.

4 голосов
/ 09 декабря 2010

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

4 голосов
/ 09 декабря 2010

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

У вас есть указатели или int s в структуре?Указатели будут недействительными в новом процессе при обратном чтении, и формат int не одинаков на всех машинах (если не считать две проблемы с остановкой показа при таком подходе).Все, что указано как часть графа объекта, не будет обработано.Упаковка структуры может быть разной на целевой машине (32-разрядной или 64-разрядной) или даже из-за изменения параметров компилятора на том же оборудовании, что делает sizeof(Account) ненадежным в качестве размера данных для чтения.

Длялучшее решение, посмотрите на библиотеку сериализации , которая решает эти проблемы для вас. Boost.Serialization * Хороший пример .

Здесь мы используем термин «сериализация» для обозначения обратимой деконструкции произвольного набора структур данных C ++ в последовательность байтов.,Такая система может использоваться для восстановления эквивалентной структуры в другом программном контексте.В зависимости от контекста для этого может использоваться постоянство объектов, удаленная передача параметров или другие средства.

Буферы протокола Google также хорошо работает для простых иерархий объектов.

1 голос
/ 09 декабря 2010

Нет.

Для того, чтобы это работало, структура должна быть POD (простые старые данные: только простые члены данных и члены данных POD, никаких виртуальных функций ... возможно, некоторые другие ограничения, которыене могу вспомнить).

Поэтому, если вы хотите это сделать, вам понадобится такая структура:

struct Account {
    char login[20];
    char password[20];
};

Обратите внимание, что std :: string не POD, поэтомунужны простые массивы.

Тем не менее, не очень хороший подход для вас.Ключевое слово: "сериализация":).

1 голос
/ 09 декабря 2010

Этот метод, если он вообще работает, далеко не надежен.Гораздо лучше определиться с какой-нибудь «сериализованной» формой, будь то двоичная, текстовая, XML и т. Д., И записать ее.

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

1 голос
/ 09 декабря 2010

Это не заменит правильную сериализацию.Рассмотрим случай любого сложного типа, который содержит указатели - если вы сохраните указатели в файл, когда вы загрузите их позже, они не будут указывать на что-либо значимое.

Кроме того, он может сломатьсяесли код изменяется или даже если он перекомпилирован с другими параметрами компилятора.

Так что он действительно полезен только для кратковременного хранения простых типов - и при этом он занимает гораздо больше места, чем необходимо для этогозадача.

0 голосов
/ 09 декабря 2010

Некоторая версия строки не использует динамическую память для строки, когда строка мала.Таким образом, храните строку внутри строкового объекта.

Подумайте об этом:

 struct SimpleString
 {
     char*    begin;        // beginning of string
     char*    end;          // end of string
     char*    allocEnd;     // end of allocated buffer end <= allocEnd
     int*     shareCount;   // String are usually copy on write
                            // as a result you need to track the number of people
                            // using this buffer
 };

Теперь в 64-битной системе.Каждый указатель составляет 8 байтов.Таким образом, строка размером менее 32 байт может вписаться в одну и ту же структуру без выделения буфера.

 struct CompressedString
 {
     char buffer[sizeof(SimpleString)];
 };
 stuct OptString
 {
     int      type;        // Normal /Compressed
     union
     {
         SimpleString     simple;
         CompressedString compressed;
     }
 };

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

Очевидно, что это не переносимо, поскольку это зависит от деталей реализации std :: string.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...