Archived

This topic is now archived and is closed to further replies.

Save/Load routine failing. some help?

This topic is 5576 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a CPlayer class created and I'm trying to write it to a file and read it back using fwrite and fread. If I write to file, then read it back in the same run, it works perfectly. If I write to file. Close, then just run the code to read the file, some of the variables don't read right. Here is the code...
      
//My CPlayer Class

class CPlayer
{
public:
//Constructor & Destructor

	CPlayer();
	~CPlayer();
//General Variables

	string	name;	//Players Name

	string	race;	//Players Race

	string	Class;	//Player Class

	int		gold;
	int		exp;
	int		level;
//Stat Variables

	int		hp;		//Current hitpoints

	int		maxhp;	//Max hitpoints

	int		mp;		//Current magic points

	int		maxmp;	//Max magic points

	int		STR;	//Players Strength

	int		DEX;	//Players Dexterity

	int		INT;	//Players Intellegence

	int		CON;	//Players Constitution

	int		WIS;	//Players Wisdom  

	int		CHA;	//Players Charisma

	int		hitdie;	//Players Hit Point Die


//Class Functions

	void	Create(bool isnpc, CPlayer* npc);				//Character Creation

	void	LevelUp();				//Character Gains a Level

	void	Move(int direction);	//Character Moves on Combat Map

	void	View();
};

////////////////////////////////////

////////////////////////////////////


//My main() Function

/////////////////////

int main()
{ //First part of Code

	srand((unsigned) time(NULL));
	CPlayer Player0;
	CParty Party;
	Party.player[0] = &Player0;
	Party.player[0]->Create(0, NULL);
	Party.player[0]->View();
	save(Party.player[0]);
	//Second Part of Code

	CPlayer PlayerLoaded;
	CPlayer* pPlayerLoaded = &PlayerLoaded;
	load(pPlayerLoaded);
	cout<<"\n\nname = "<<PlayerLoaded.name<<endl;

	return 0;
}
  
If I run my code like this, it will write 'Party.player[0]' to file, then it will read it back in to pPlayerLoaded. That works fine. However, if I run just the first part of the code (Up to just past the Save routine), then re-run with just the Load code, some members of the CPlayer class, mainly the name, race, and class, are not reading back in... when it goes to display the name member, it's just gibberish. is this a problem with std::string not reading back in right? the int's seem fine. Following are the save/load routines.
        
//SAVE ROUTINE

////////////////

void save(CPlayer* theplayer)
{
	FILE *savefile;

	if( (savefile = fopen( "testfile.txt", "wb" )) != NULL)
	{
		//File Opened or Created

		fwrite(theplayer, sizeof(CPlayer), 1, savefile);
		cout<<"\nDone Saving\n";
		fclose(savefile);
	}
	else
	{
		cout<<"Couldn't open savefile"<<endl;
	}
}
//////////////


//LOAD ROUTINE

//////////////////

void load(CPlayer* theplayer)
{
	FILE *savefile;

	if( (savefile = fopen( "testfile.txt", "rb" )) !=NULL)
	{
		fread(theplayer, sizeof(CPlayer), 1, savefile);
		cout<<"\nDone Loading\n";
		fclose(savefile);
	}
	else
	{
		cout<<"Couldn't open file for loading"<<endl;
	}
}
      
Any suggestions? ~~~~~~~~~~~ Chris Vogel ~~~~~~~~~~~ --edit typos [edited by - Radagar on September 10, 2002 11:07:22 PM] [edited by - Radagar on September 10, 2002 11:07:53 PM]

Share this post


Link to post
Share on other sites
I don't know, but I think that the problem is that the strings are kinds arrays, so when you tell the computer to write the class, it will write a pointer to the array of chars in the file, instead of writing the array itself. I guess you will have to manually save each string. I don't know very well how to do that, but I think there's a funtion similar to sizeof() that will work on strings (don't use sizeof() here, it will only work with constants arrays, with is not the case of string). Try somehting like:


      
fwrite(theplayer, sizeof(CPlayer), 1, savefile);
fwrite(theplayer.name, GetStringSize(name), 1, savefile);
fwrite(theplayer.class, GetStringSize(class), 1, savefile);
fwrite(theplayer.race, GetStringSize(race), 1, savefile);
cout<<"\nDone Saving\n";
fclose(savefile);


However, I never really used strings, so I don't know if they are closer to be something like a class, or something closer to a array of chars.


[edited by - algumacoisaqualquer on September 11, 2002 2:18:10 AM]

Share this post


Link to post
Share on other sites
quote:
Original post by Alpha_ProgDes
i know that std::string has problems with C file reads and writes.
every post i see says append .c_str() to the string (i believe).
hopefully that helps


well, from what i gather he''s not passing a string to
his save/load function.. and if he did, he''d get a compiler
error since fread/fwrite takes a const char*



-eldee
;another space monkey;
[ Forced Evolution Studios ]


::evolve::

Share this post


Link to post
Share on other sites
man, the help tonight ... you see that he''s outputing a fixed size, and reading back in a fixed size ... isn''t that a clue ...

here''s the deal ... you are using simple binary writing and reading functions, which work just like memcpy, but to a file ... right ...

BUT YOUR STRUCTURE HAS POINTERS IN IT ... and pointers cannot be stored out to a file and used again later ...they don''t mean anything later ... and the actual data pointed to won''t be there ...

think about it ... if you had a class like this:

struct BinarySafe {
int x;
int y;
char name[30];
};

then you can see that it works just fine .. cause it would be 38 bytes long, including all the data, and nothing but data ...

but if you had this:

struct UsesPointers {
int x;
int y;
char *name;
};

then you would be writing and reading 12 bytes ... the last 4 of which are just a pointer .. and NONE of which is a name.

this same thing would be true of any situation using pointers, and should be assumed to be true of any situation using classes ... (because you cannot copy classes into each other binary, you MUST use constructors) ....

the ANSWER is that you must go a level deeper to output each of your poitner or class objects ... for example, to read and write the UsesPointers struct above , you might do this:


  
void write_c_str(FILE *file, char *str)
{
short len = strlen(str); // get the string length

fwrite(&len, sizeof(len), 1, file); // write the string length

fwrite(str, len, 1, file); // write the string

}

void read_c_str(FILE *file, char **str)
{
short len;
fread(&len, sizeof(len), 1, file); // read the string length

delete [] *str; // release any old memory in str

*str = new char[len]; // allocate enough space for new string

fread(*str, len, 1, file); // output the string

}

void save(UsesPointers *up)
{
fwrite(up->x, sizeof(up->x), 1, file);
fwrite(up->y, sizeof(up->y), 1, file);
write_c_str(file, up->name);
}

void load(UsesPointers **up)
{
fread(up->x, sizeof(up->x), 1, file);
fread(up->y, sizeof(up->y), 1, file);
read_c_str(file, &(up->name));
}


Good Luck ... and these are just samples ... but I hope you see what I''m talking about now ...

Share this post


Link to post
Share on other sites
You should not ever try to convert instances of classes to a simple void* pointer, that will break the encapsulation of the class at minimum. It could also have other sideeffects, like the ones you are experiencing.

Share this post


Link to post
Share on other sites
Thanks everyone. I''ll try writing and reading each member of the class instead of the actual class. I see how having pointers in teh class would screw things up, since the pointers don''t mean anything later. Explains why it DOES work when I save and load on the same run too.



~~~~~~~~~~~
Chris Vogel
~~~~~~~~~~~

Share this post


Link to post
Share on other sites
quote:
Original post by eldee
you could save yourself alot of trouble and avoid having to
update your code later if you switched to a c++ standard method
for file i/o...

use fstream!!

-eldee
;another space monkey;
[ Forced Evolution Studios ]


::evolve::





Would using FSTREAM make it any easier to write out a full class with pointers, without writing each member of that class seperately? I''ve not used FSTREAM, and I''m not familiar with it''s syntax.



~~~~~~~~~~~
Chris Vogel
~~~~~~~~~~~

Share this post


Link to post
Share on other sites
Radagar:

Using strings with fstream is fine. If you use the string class, they are not treated as arrays, but as a data type. Here's an example I whipped up that shows using fstream with a class that contains a string. First, the header:


      
//test.h


#include <string>
using namespace std;

class CTest {

string test_name;
int test_num;

public:

string get_test_name() { return test_name;};
int get_test_num() {return test_num;};
void set_test_name(string name) {test_name = name;};
void set_test_num(int num) {test_num = num;};

CTest() { };
~CTest() { };

};


and the test.cpp file:


        
//test.cpp


#include <iostream>
#include <fstream>
#include <string>
#include "test.h"
using namespace std;

int main(); //prototype


int main() {

CTest test1, test2, test3, test4;
char file[] = "testfile";
char *file_ptr;
string name1("name1");
string name2("name2");
string name3, name4;
int three, four;

file_ptr = file;

test1.set_test_name(name1);
test1.set_test_num(1);

test2.set_test_name(name2);
test2.set_test_num(2);

//open the file to write


ofstream outfile(file_ptr, ios::out | ios::binary);

if (!outfile) {

//couldn't open file

cout << "couldn't open file" << endl;

}

outfile.write((char *) &test1, sizeof(CTest));
outfile.write((char *) &test2, sizeof(CTest));

outfile.close();

//open the file to read


ifstream infile(file_ptr, ios::in | ios::binary);

if (!infile) {

//couldn't open file

cout << "couldn't open file" << endl;

}

infile.read((char *) &test3, sizeof(CTest));
infile.read((char *) &test4, sizeof(CTest));

infile.close();

name3 = test3.get_test_name();
three = test3.get_test_num();

name4 = test4.get_test_name();
four = test4.get_test_num();

cout << "test1 and test3's name is: " << name3 << endl;
cout << "test1 and test3's number is: " << three << endl;
cout << "test2 and test4's name is: " << name4 << endl;
cout << "test2 and test4's number is: " << four << endl;

}


I got the expected output, which was:

test1 and test3's name is: name1
test1 and test3's number is: 1
test2 and test4's name is: name2
test2 and test4's number is: 2

I would suggest that you use the C++ stream I/O classes rather than the old C I/O functions.

John.

[edited by - JohnAD on September 11, 2002 5:32:27 PM]

[edited by - JohnAD on September 11, 2002 5:33:44 PM]

Share this post


Link to post
Share on other sites
Thanks JohnAD. I was goign that route when you posted, but I wasn''t sure of the format for the fstream class and input/output. Your example helps a lot.

But... I''m a little worried it might not work based solely on your example. My example above worked fine when ran like it was, because the pointers to chars inside the strings that I created were not getting reset. But if you run the part of the code to write the class out to file and close.. then restart and run just the code to read it in, the pointers don''t mean anything and any string part of the class is invalid data.

This may not be a problem when using fstream, I''m going to test it... But your program above doesn''t necessarily prove that it will work since it is writing to the file then reading from it in the same run when the pointers are still valid, as my program above does.

And there is also the possibility that I''m largely confused and don''t know what I''m talking about =)

Thanks for the code though, I''m going to try using fstream and see if it works!



~~~~~~~~~~~
Chris Vogel
~~~~~~~~~~~

Share this post


Link to post
Share on other sites
Radagar:

I just tested a version that only reads the file, and came up with the same error that you got. One way to work around this would be to create a capatible class that stored the character array, and just copy the values over before saving.

John.

[EDIT] I hate typing while I'm in a hurry...[/EDIT]

[edited by - JohnAD on September 11, 2002 12:03:55 AM]

Share this post


Link to post
Share on other sites
Yeah...the string pointers are likely causeing the goof.

Some things that you might want to consider:

Make the characters race and class integers that can be used as indexes in the appropriate arrays (define as CONSTants?)...this way you can save further headackes down the road...

say a value of 1 = a ''human''...a 2 = ''ork''...3 = ''elf''...whatever...then in the game if there is anything associated with the class that must be performed (a magic spell that only effects orks, for example) then you can check the race statis of the player useing the value rather then a string...this way there won''t be any suprises if somewhere in your code you missspelled (and/or capitalized) the word ''Ork''.
Same goes for class...

Also...and this would be easy to do

You can keep the class just as it is right now (change race and class to integers, of course)...and still save the file correctly, without errors on loading it back in again.

simply use the name of the character for the name of the file...make the file location something unique (like ''.mysave'' etc..) for all character save files...then you can save the character as is (pointer and all) without problems.

Loading the file back in will be more difficult...first set up a local pointer to a location to temporarily hold the character name...then let the player select the file to load ("*.mysave" to bring up a list of files,etc..)...once selected...read everything in the file name before the ''.'' into the local name string ...then load the file in...then change the file loaded name (which would contain garbage at this point) to the local name...deleate the temporary local name and you are done.

Course there are file name length limitations and such...but you have given the player the ability to then make back-up copies of specific characters-n-such without adding more game code in doing so

Share this post


Link to post
Share on other sites
You could build on this simple (?) sample for reading/writing objects from/to streams. Just overload Read and Write for your specific class (as done for std::string) and it should work fine. It may not be the fastest solution there is.

  

#include <iostream>

#include <string>

// T must not be a pointer nor a struct/class that contains

// pointers nor a struct/class that have a vtable (virtual methods).

template <class T>
bool Write(std::ostream& rStream, const T& rObject)
{
rStream.write((const char*) &rObject, sizeof(T));
if (!rStream)
{
return false;
}
return true;
}

// T must not be a pointer nor a struct/class that contains

// pointers nor a struct/class that have a vtable (virtual methods).

template <class T>
bool Read(std::istream& rStream, T& rObject)
{
rStream.read((char*) &rObject, sizeof(T));
if (!rStream)
{
return false;
}
return true;
}

bool Write(std::ostream& rStream, const std::string& rString)
{
size_t uiSize = rString.size();
if (!Write(rStream, uiSize))
{
return false;
}
for (std::string::const_iterator pIter = rString.begin(), pIterEnd = rString.end(); pIter != pIterEnd; ++pIter)
{
if (!Write(rStream, *pIter))
{
return false;
}
}
return true;
}

bool Read(std::istream& rStream, std::string& rString)
{
size_t uiSize;
if (!Read(rStream, uiSize))
{
return false;
}
rString.resize(uiSize);
for (std::string::iterator pIter = rString.begin(), pIterEnd = rString.end(); pIter != pIterEnd; ++pIter)
{
if (!Read(rStream, *pIter))
{
return false;
}
}
return true;
}

Share this post


Link to post
Share on other sites
Thanks all! I''ll use the suggestion of making them INT''s and just looking up the string values from an array. That works out better later on as you said.

As for the idea of assigning the name based on the name of the file, that''s pretty good, but it would be harder for the race and class, and it wouldn''t really work with my current design plan. Thanks for the suggestion though!

GameDev is a great community, I can always get help here. Until my next problem, Laters!

~~~~~~~~~~~
Chris Vogel
~~~~~~~~~~~

Share this post


Link to post
Share on other sites
quote:
Original post by jkeppens
I've never worked with templates before, do maybe a stupid question : how would you implement something like that ?

No question is too stupid to ask, only the answers.

Put the code I wrote in a header, include it where you are streaming binary data and do like this for example:

  
#include <theheaderwiththestuffiwroteealier.h>

class Person
{
public:
Person(const std::string& rstrName, int iAge)
: mstrName(rstrName),
miAge(iAge)
{
}

Person(std::istream& rStream)
{
if (!Read(rStream, *this))
{
throw std::exception("Failed to construct Person from from stream");
}
}

const std::string& GetName() const { return mstrName; }
int GetAge() const { return miAge; }

void SetName(const std::string& rstrName) { mstrName = rstrName; }
void GetAge(int iAge) { miAge = iAge; }

friend bool Write(std::ostream& rStream, const Person& rPerson);
friend bool Read(std::istream& rStream, Person& rPerson);

private:
std::string mstrName;
int miAge;
};

bool Write(std::ostream& rStream, const Person& rPerson)
{
if (!Write(rStream, rPerson.mstrName))
{
return false;
}
if (!Write(rStream, rPerson.miAge))
{
return false;
}
return true;
}

bool Read(std::istream& rStream, Person& rPerson)
{
if (!Read(rStream, rPerson.mstrName))
{
return false;
}
if (!Read(rStream, rPerson.miAge))
{
return false;
}
return true;
}

int main()
{
Person person1("Dalleboy", 24);
std::ofstream stream1("c:/person.dat", std::ios::binary);
if (!Write(stream1, person1))
{
std::cerr << "Failed to write person to stream" << std::endl;
return 1;
}

stream1.close();

std::ifstream stream2("c:/person.dat", std::ios::binary);
Person person2(stream2);

std::cout << "Name: " << person1.GetName() << ", Age: " << person1.GetAge() << std::endl;
std::cout << "Name: " << person2.GetName() << ", Age: " << person2.GetAge() << std::endl;

return 0;
}


[edited by - dalleboy on September 12, 2002 11:26:48 AM]

Share this post


Link to post
Share on other sites