С ++ программа падает при чтении объекта из файла произвольного доступа - PullRequest
0 голосов
/ 30 октября 2010

У меня есть следующий User.h, который содержит несколько атрибутов (строк). User.cpp имеет все определения.

//User.h
#ifndef USER_H
#define USER_H
#include<iostream>
#include <cstring>

using namespace std;

class User{

  string username;

  public:
         User();
         string getUsername() const;                      
         void setUsername(string);

};
#endif

Я использую другой класс «Файл» для вставки новых пользователей / просмотра пользователей из файла .dat со случайным доступом.

//File.h
#ifndef FILE_H
#define FILE_H
#include "User.h"

class File{
    public:

             void loadUser(); 
             bool addUser(User&); 

};
#endif

Определения классов файлов

//File.cpp
#include<cstring>
#include<iostream>
#include<iomanip>
#include<fstream>

#include "User.h"
#include "File.h"

using namespace std;

User tempUser;
fstream usersFile;

void File::loadUser(){
     usersFile.open("users.dat", ios::in | ios::binary);

     usersFile.seekg(0);  

     // commenting the following lines prevented the issue
     usersFile.read(reinterpret_cast<char *>(&tempUser), sizeof(tempUser)); 

     cout<<tempUser.getUsername().c_str(); 

     usersFile.close();
}

bool File::addUser(User& user){

     usersFile.open("users.dat", ios::out | ios::ate | ios::binary);

     // no issue when writing to file
     usersFile.write( reinterpret_cast<const char *>(&user), sizeof(user));

     usersFile.close();

     cout<<"User added";  
}

Я получаю вышеупомянутую проблему при запуске. Нет проблем с компиляцией.

Есть ли проблема при работе с объектами, имеющими "строковые атрибуты" внутри, при обработке?

Пожалуйста, помогите

Ответы [ 5 ]

1 голос
/ 30 октября 2010

Я думаю, проблема в том, что вы смешиваете код C ++ с мышлением C.То, что вы действительно должны здесь делать, это использовать оператор извлечения opeartor>>() вместе с потоками ввода-вывода C ++.Это в отличие от использования стандартного ввода-вывода C вместе с функцией read().Для записи объекта используйте оператор вставки operator<<() вместо функции C write().

Для работы со строками используйте std::string.Этот класс обеспечивает operator<<() и operator>>().Таким образом, вы можете сказать std::string s, а затем io << s и io >> s, где io - это некоторый объект потока ввода-вывода C ++.Это будет делать правильную вещь (тм).Философия здесь заключается в том, что класс std::string знает лучше, чем вы, пользователь, как сериализовать объект std::string.Так что давайте сделаем это, используя операторы << и >>.

Продолжая эту идею, вы, как автор User, лучше других знаете, как сериализовать объект User,Поэтому предоставьте операторы << и >> для пользователей вашего класса в качестве услуги.«Пользователи вашего класса» вполне могут быть вами через неделю, когда вы полностью забыли, как правильно сериализовать объект User.(Или вы думаете, что помните, но на практике вы забыли детали, что привело к ошибке в вашем коде).Пример:

// in User.h
#include <string>
#include <iosfwd>  // forward declarations of standard IO streams

namespace mine {
class User {
    User(const std::string& name) : username(name) { }
    friend std::ostream& operator<<(std::ostream&, const User&);
    friend std::istream& operator>>(std::istream&, User&);

private:
    std::string username;
};

std::ostream& operator<<(std::ostream& out, const User& u)
{
    return out << u.username;
}

std::istream& operator>>(std::istream& in, User& u)
{
    return in >> u.username;
}
}  // namespace mine

С этого момента, чтобы сохранить пользователя в файл, который вы говорите

std::ofstream f("filename");
User u("John");
f << u;

Вот и все.Чтобы прочитать пользователю:

std::ifstream f2("filename");
f2 >> u;

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

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

Конечно, некоторые скажут, что даже это плохая идея.Я лично думаю, что это хорошо, если вы знаете, что в этом конкретном файле реализации могут возникнуть конфликты имен.Но, по крайней мере, вы знаете, где находится оператор using: он находится в вашем файле реализации и вызывает только конфликты в этом файле реализации.Это своего рода пистолет (пластиковый водяной пистолет, но, тем не менее, пистолет), только вы можете стрелять (мокрыми) собственными ногами, а не другими.Что, на мой взгляд, совершенно нормально.

1 голос
/ 30 октября 2010

Вы не можете читать такие не POD-типы. Строка не относится к типу POD. Что такое типы POD в C ++?

Есть несколько способов правильно читать строки, в зависимости от того, как они хранились.

Для текстовых файлов:

Если строка представляет собой одно слово, разделенное с обеих сторон пробелами, вы можете использовать простой старый оператор >>. Если это более одного слова, вы можете сохранить его в отдельной строке и использовать getline.

Для двоичных файлов:

Сохранить строку в завершённой форме. Читайте по одному символу за раз, проверяя наличие нулевого символа. Или добавьте в строку целое число, хранящее ее размер. Когда вы читаете его, сначала прочитайте целое число, а затем прочитайте столько символов.

0 голосов
/ 30 октября 2010

строка - это объект, что означает, что вы не пишете его содержимое.

Попробуйте написать пользователя и изучить файл, чтобы понять, что я имею в виду. Затем вы читаете некоторые указатели, указывающие на недопустимые области памяти.

0 голосов
/ 30 октября 2010

Вместо того, чтобы создавать свой собственный код сериализации, что-то вроде Буферы протокола Google может делать то, что вы хотите, с меньшими усилиями. Он идеально подходит для передачи простых структурированных данных с места на место.

0 голосов
/ 30 октября 2010

Да, класс std :: string не является обычными старыми данными, другими словами, он содержит указатели. Если вы сохраните / загрузите класс строки таким образом, данные , указывающие на , не будут загружены / сохранены, только значение указателя.

Кроме того, sizeof (tempUser) не будет включать размер текста, на который указывают строки.

Ваше решение - изменить способ чтения / записи данных. Одним из способов является использование boost :: serialization, который обрабатывает типы данных, такие как std :: string. Другим способом было бы написать каждую строку в отдельной строке в текстовом документе (не в двоичном режиме), а затем использовать readline, чтобы прочитать их обратно.

...