Итак, прежде чем дать вам решение, давайте кратко поговорим о том, что здесь происходит:
ofs.write((char*)&t1,sizeof(t1));
Что вы делаете, это приведение t1 к указателю на char,и сказать «записать в оф-представление памяти t1, как есть».Таким образом, мы должны спросить себя: что это за представление в памяти t1?
- Вы храните целое число (определенное реализацией, скорее всего, 4 байта)
- Вы также храните комплексобъект std :: string.
Запись 4-байтового целого числа может быть в порядке.Он определенно не переносим (big-endian против little endian), и вы можете получить неправильное значение int, если файл читается на платформе с другим порядком байтов.
Запись std::string
определенно не подходит,Строки являются сложным объектом, и они чаще всего выделяют память в куче (хотя существует такая вещь, как оптимизация небольших строк).Это означает, что вы собираетесь сериализовать указатель на динамически размещенный объект.Это никогда не будет работать, так как чтение указателя назад будет указывать на какое-то место в памяти, которое вы абсолютно не можете контролировать.Это отличный пример неопределенного поведения.Все идет, и все может случиться с вашей программой, в том числе «кажется, работает правильно», несмотря на глубоко укоренившиеся проблемы.В вашем конкретном примере, поскольку созданный объект Telephone все еще находится в памяти, вы получаете 2 указателя на одну и ту же динамически распределенную память.Когда ваш объект temp
выходит из области видимости, он удаляет эту память.
Когда вы возвращаетесь к своей основной функции, когда t1
выходит из области видимости, он пытается удалить ту же самую память снова.
Сериализация любого рода указателей - это большое нет-нет.Если ваши внутренние объекты состоят из указателей, вам нужно принять решение о том, как эти указатели будут храниться в вашем потоке, а затем прочитать, чтобы создать новый объект.Распространенным решением является сохранение их «как будто», они были сохранены по значению, а затем при чтении объекта из хранилища динамически распределяют память и помещают содержимое объекта в ту же память.Это, очевидно, не будет работать, если вы попытаетесь сериализовать случай, когда несколько объектов указывают на один и тот же адрес в памяти: если вы попытаетесь применить это решение, у вас будет несколько копий вашего исходного объекта.
К счастью, для случая std::string
эта проблема легко решается, поскольку строки перегружены operator<<
и operator>>
, и вам не нужно ничего реализовывать, чтобы заставить их работать,
edit: просто использование operator<<
и operator>>
не сработает для std::string
, чуть позже объяснено почему.
Как сделатьэто работает:
Есть много возможных решений, и я собираюсь поделиться здесь.Основная идея заключается в том, что вы должны сериализовать каждого члена вашей структуры телефона в отдельности и полагаться на тот факт, что каждый участник знает, как сериализовать себя.Я собираюсь игнорировать проблему совместимости с перекрестным порядком байтов, чтобы сделать ответ немного более коротким, но если вы заботитесь о кросс-платформенной совместимости, вам следует подумать об этом.
Мой основной подход - переопределить operator<<
и operator>>
для класса телефона.
Я объявляю две бесплатные функции, которые являются друзьями класса «Телефон».Это позволило бы им тыкать во внутренние части различных телефонных объектов, чтобы сериализовать их участников.
class Telephone {
friend ostream& operator<<(ostream& os, const Telephone& telephone);
friend istream& operator>>(istream& is, Telephone& telephone);
// ...
};
edit: у меня изначально был код для сериализации строк неправильно, поэтому мой комментарий, что это довольно просто, просто неверен
Код для реализации функций имеет неожиданный поворот.Потому что operator>>
для строк прекращает чтение из потока при обнаружении пробела, если имя, которое не является ни одним словом, или со специальными символами, не будет работать, и приведёт поток в состояние ошибки, не считывая номер телефона,Чтобы обойти проблему, я последовал примеру @Michael Veksler и явно сохранил длину строки.Моя реализация выглядит следующим образом:
ostream& operator<<(ostream& os, const Telephone& telephone)
{
const size_t nameSize = telephone.name.size();
os << nameSize;
os.write(telephone.name.data(), nameSize);
os << telephone.phno;
return os;
}
istream& operator>>(istream& is, Telephone& telephone)
{
size_t nameSize = 0;
is >> nameSize;
telephone.name.resize(nameSize);
is.read(&telephone.name[0], nameSize);
is >> telephone.phno;
return is;
}
Обратите внимание, что вы должны убедиться, что данные, которые вы пишете, соответствуют данным, которые вы собираетесь позже попытаться прочитать.Если вы храните другой объем информации, или аргументы находятся в неправильном порядке, вы не получите правильный объект.Если вы позже внесете какие-либо изменения в класс «Телефон», добавив новые поля, которые вы хотите сохранить, вам нужно будет изменить обе функции.
Для поддержки имен с пробелами также следует изменить способ чтения имен из cin.Один из способов - использовать std::getline(std::cin, name);
вместо cin >> name
Наконец, как вам следует сериализовать и десериализовать из этих потоков: не используйте функции ostream::write()
и istream::read()
- используйте вместо operator<<
и operator>>
, которые мы переопределили.
void getData() {
Telephone temp;
ifstream ifs("Sample.txt",ios::in|ios::binary);
ifs >> temp;
temp.displayData();
}
void storeData(const Telephone& telephone) {
ofstream ofs("Sample.txt",ios::out|ios::binary);
ofs << telephone;
}