1

These methods are supposed to save and load the entirety of the object they're associated with. When I compile the program under Linux through gcc, the save seems to work but it segfaults when loading. When I compile it under Windows through the Visual Studio compiler, it works like a dream. I am not sure what the differences are, but I've got a hunch that it involves some gcc oddity.

The two methods:

void User::SaveToFile()
{
  ofstream outFile;
  string datafile_name = username + "_data";
  outFile.open(datafile_name.c_str(), ios::binary);
  outFile.write((char*)this, sizeof(*this));
}
void User::LoadFromFile(string filename)
{
  ifstream inFile;
  inFile.open(filename.c_str(), ios::binary);
  inFile.read((char*)this, sizeof(*this));
}

The declaration:

class User
{
private:
  string username;
  string realname;
  string password;
  string hint;
  double gpa;
  vector<Course> courses;
public:
  double PredictGPA();
  void ChangePassword();
  void SaveToFile();
  void LoadFromFile(string filename);

  void SetUsername(string _username){username = _username;}
  string GetUsername(){return username;}
  void SetRealname(string _realname){realname = _realname;}
  string GetRealname(){return realname;}
  void SetPass(string _password){password = _password;}
  string GetPass(){return password;}
  void SetHint(string _hint){hint = _hint;}
  string GetHint(){return hint;}
};
12
  • 1
    Is User a POD-type? Does it have the same memory layout with both gcc and vc? Commented Nov 7, 2011 at 18:01
  • 1
    Please give the class declaration Commented Nov 7, 2011 at 18:02
  • 1
    Probably not a GCC "oddity", but some assumption you've made that doesn't hold. Looking at the broad abuse of casting, that seems likely! Anyway, just note that "binary serialisation" is 100% non-portable and you should look at text-based serialisation instead unless you have a really good reason not to. Commented Nov 7, 2011 at 18:03
  • 1
    Why would you expect a vector to serialize the same way on GCC and visual studio? Commented Nov 7, 2011 at 18:11
  • 2
    @Dahud: Ironic! You're far more likely to run into problems with binary-packed C++ objects than with serialising into a robust text-based format, such as JSON. Commented Nov 7, 2011 at 18:14

5 Answers 5

2

Your class User is not a POD type, its not a Plain Old Data type (as C structs are). You cannot just read and write its memory bitwise and expect it to work. Both string and vector are not PODs, they keep pointers to their dynamically allocated data. When reading those back, attempts to access invalid memory will result in a segfault. What's more, the contents of both the string and vector are not actually being saved at all, since they are not within the memory layout of the object (it may work sometimes with string with SBO, but its just but chance and still undefined to do it).

Sign up to request clarification or add additional context in comments.

6 Comments

It probably works (sometimes) on Visual Studio due to the Small String Optimization.
@Mooing Duck: Yep, that's what SBO stands for in my answer.
Even with some PODs ( eg structs) you cannot write memory bitwise and expect it to work. Compilers may pad it differently. Even the same compiler with different settings.
So the best way to solve this problem would be to simply save text that describes the object?
@Tomalak Geret'kal, Mooing Duck: SSO is just a particular application of SBO to strings.
|
2

You would need a way to serialize and deserialize your class; your class can't magically become an object when you read it in like that.

Instead you would need to supply to functions that you call when loading/saving your class that store the class in some format of your choosing e.g. XML.

so instead of

outFile.write((char*)this, sizeof(*this));

have some member function to convert it to a string with some format that you easily can parse when you load it (or some binary format whatever you find easier), then save it.

outFile.write(this->myserialize(), mysize);

1 Comment

Also mysize will typically change and should be generated from the myserialize() function. For many formats would also be a good idea to have in header of char array returned from myserialize() for loading.
1

You can't write into string like that. For one thing it usually stores its data dynamically, i.e. not inside the object at all, and for another you shall not rely on any particular layout of it.

There are similar issues with vectors, and you don't appear to have considered endianness and padding at all.

Put simply, you're making assumptions that do not hold.

In general, do not mess with complex (non-POD) objects on the byte level. Serialise with some text format instead, using the objects' public member functions to extract and restore their state.

Have you considered JSON?

Comments

1

Things like strings etc may contain pointers - in which case your method can go horribly wrong.

You need to serialise the data - I.e. convert it to a series of bytes.

Then when reading the data you just read the bytes and then create the object from that. The new pointers will be correct.

Comments

0

If you stay with this route I would write the length of the string instead of null terminating it. Easier to allocated on loading. There is alot to consider in a binary format. Each field should have some type of ID so it can be found if in wrong spot or a different version of your program. Also at the beginning of your file write what endianess you are using and the size of your integers etc. Or decide a standard size and endianess for everything. I use to write code like this all the time for networking and file storage. There are much better modern approaches. Also consider using a buffer and creating Serialize() function.

Good modern alternatives include :SQLite3, XML, JSON

Untested Example:

class object
{

Load()
{
  ifstream inFile;
  int size;

  inFile.open("filename", ios::binary);

  inFile.read(&size, 4);  
  stringA.resize(size);
  inFile.read(&stringA[0], size);

  inFile.read(&size, 4);  
  stringB.resize(size);
  inFile.read(&stringB[0], size);

  inFile.close();          //don't forget to close your files
}
Save()
{
  ofstream outFile;
  int size;

  outFile.open("filename", ios::binary);

  size = stringA.size();   
  outFile.write(&size, 4);
  outFile.write(&stringA[0], size);

  size = stringB.size();       
  outFile.write(&size, 4);
  outFile.write(&stringA[0], size);
  outFile.close();
}

private:
std::string stringA
std::string stringB
};

2 Comments

Thing is, this is for an Intro C++ class. I've gone a bit extra already; these kids didn't know what a vector was when I tried to implement one. JSON or XML might be a bit much.
@Dahud: Sounds like it's a bit much for you too if you are using vectors so incorrectly :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.