Load multiple class objects from file OR save/load vector of class objects

Hi there

Stuck again...

I have a class for player characters and store all instance of this class in a vector. So far so good.

Now I want to store all player class instances in a file and load them from that file the next time the program is run. I already successfully stored one instance and loaded it back. However I'm now stuck as I can't figure out how to do this with multiple instances of the player class (while still using only one file).

Now my preferred option would be to store the entire vector and then just read that back into the program. However, this did not work at all when I tried it so I opted for another approach.

Right now, I store each new instance at the end of the file by using the append function of "ofstream".

However, I cannot for the life of me figure out how to read the individual instances back. I mean I can get one, the first, but not the others.

So how the hell do I do that?

More precisely:

How do I store and load my entire vector?

or alternatively:

How do I access and load individual objects from a file, ideally starting at the beginning and then iterate through each object until I reach the end of the file?

Below some example code (not executable) to show what I have so far.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// the class looks something like this 
// hugely simplified, just to show what kind of stuff is in the class

class C_Player
{
private:
	std::string sPlayerName;
	int Gender;		
	int Age;		

public:
	C_Player();	
	~C_Player();

	std::vector<C_Weapon> v_PlayerWeaponInventory;		
	std::vector<C_Weapon>::iterator WeaponIterator;	

	void EquipItem(int ItemID, bool TwoHand);
	void ShowData(int Data);
}

// the relevant code in the main function 
// again only the relevant stuff and hugely simplified

int main()
{
	std::vector<C_Player> v_PlayerList;					
	std::vector<C_Player>::iterator i_PlayerIterator;

	// create new player and save to file (append after already existing players)

	C_Player Player;

	v_PlayerList.push_back(Player);	

	std::ofstream Output("PlayerList.pl", std::ios::binary | std::ios::app);

	Output.write((char*)&Player, sizeof(Player));

	Output.close();

	// load existing player from file 
	// this is how far I got without the code causing errors or crashing the program

	std::ifstream Input("PlayerList.pl", std::ios::binary);

	Input.read((char*)&Player, sizeof(Player));

	v_PlayerList.push_back(Player);	

	Input.close();
}
Last edited on
I think the easiest way is to first write the size of the vector and then loop through and write all the elements. That way you know how many elements you should read.

Note that you probably want to write and read each data member of the C_Player class individually. If you are careful and only write fixed-sized integers you can make the file format platform independent. Use the same strategy as with std::vector for writing and reading the std::string.
Last edited on
I'm still stuck. I had' a Look at boost, but had to conclude that this is way more advanced stuff than what I'm used to so far.

I also continued to search the Internet for solutions but found nothing that I understood or/and could adapt to my specific case.

Further, I experimented a bit with the code I already have, which gave me a few new insights:

- Writing one instance from the vector to file and reading it back works as already stated.

- I can then create more instances, store them in the vector and access all without problem. But still, once I end the program I can then only load back one from the file. I don't know if the rest are even saved as reading the file the way I do it at the moment only ever loads the first instance.

- The content of the player inventory (the vector in line 15) does not seem to get saved along with the rest as I cannot access it on the loaded instance.

So the problem persists and there is a new one on top.

Since the first instance stored in the vector is saved and loaded without problem, I conclude that probably all the other instances get saved as well. After all, I append them manually to the end of the file, so they should be there. So the only problem that remains is how to read them back one by one past the first one. Putting them back into the vector should then be easy.

If possible I'd like to avoid writing and reading every data member individually as the class is quite large and has a lot of members (and new ones might get added later). Currently it stands at 40 variables, 7 functions and 4 vectors. If there is a possibility to just store and read the whole thing as one package, that would be very much preferred ;-)

Why does the inventory (vector from line 15) not work after loading the instance of the player-class back from file? Any ideas? Is it because it is a vector as well? Do I have to save it separately?

EDIT:

If I know the size of the file and divide that through the size of one instance, I get the number of stored instances. Can I do something with that? Basically, tell the program to read one entry, then jump so many bytes ahead, read the next, then again jump, read the next an so on until number of entries reached?

I use the following code to get these values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

std::ifstream Input("PlayerList.pl", std::ios::binary);

Input.seekg(0, std::ios::end);

int FileSize = static_cast<int>(Input.tellg());

std::cout << "\n Size of the file is " << FileSize << "bytes\n";

std::cout << " The size of Player is " << sizeof(Player) << " bytes\n";

int NumberPlayers = FileSize / sizeof(Player);

std::cout << " The Number of stored Players are: " << NumberPlayers << std::endl;


Playing around with it I already confirmed that all instances are saved as the file get's larger by the size of one instance whenever a new one is appended to it.
Last edited on
I don't know if the rest are even saved


Use a file editor that lets you view the exact byte contents of the file as dec/hex/ASCII etc. I use hex Editor Neo.
The problem is that std::vector, and sometimes also std::string, contain pointers to data elsewhere. If you write like you do you only save the pointers but not the actual data elements.
Last edited on
So, to save the contents of the players inventory, I would have to save the stuff contained therein separately. I see. Yeah, ok, once I figure out to read multiple class objects from file back to vector that shouldn't be a problem, just a lot work ;-)

So the remaining problem would be how to read multiple class objects from one file.
So the remaining problem would be how to read multiple class objects from one file.

As I think I mentioned before, you can write the number of objects before writing the objects. When you read the file later you can read this number and know how many objects you should read.

I had a try at this with a simple class.
I think you must use a fixed length string if you have a string field and want to save the data to a file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include<iostream>
#include <fstream>
#include<vector>
#include <string.h>
using namespace std;


class udt
{
	public:
	int a;
	double d;
	char s[10];
};


std::ifstream::pos_type filesize(const char* filename)
{
    std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
    return in.tellg(); 
}


void save(string filename,vector<udt> array)
{
    FILE * f = fopen(filename.c_str(), "wb");
    int size= array.size()*sizeof(array[0]);
   udt* ap = array.data();
    fwrite(ap,size,1,f) ;
    fclose(f);
}


void load(string filename,vector<udt>  &array)
{
	FILE * f = fopen(filename.c_str(), "rb");
	udt* ap = array.data();
	fread(ap,array.size()*sizeof(array[0]),1,f); 
   fclose(f);
}

void print(vector<udt>  u)
{
	int i;
	for (i=0;i<u.size();i++)
	cout<<u[i].a<<" , "<<u[i].d<<" , "<<u[i].s<<endl;
	cout<<endl;
}


int main()
{
	int i;
	vector<udt> u;
	u.resize(15);
	for(i=0;i<u.size();i++) //fill the array with stuff
	{
	 u[i].a=i;
	 u[i].d=4.77*i/3;
	 strcpy(u[i].s,"hello");
     }

    cout<<"Sent to file "<<endl;
	print(u);
	
	save("filetestre.txt",u);
	
	
	int s=filesize("filetestre.txt")/sizeof(udt); // get the number of elements
	
	cout<<"number of elements to reload "<<s<<endl;
	
	cout<<endl;
	vector<udt> out;
	out.resize(s);
	load("filetestre.txt",out);
	cout<<"Back from file "<<endl;
	print(out);
	
	//get rid
	 if( remove( "filetestre.txt" ) != 0 )
     cout<<"Error deleting file, please complete the task manually"<<endl;
     else
     cout<< "File successfully deleted"<<endl;
  
  
cout <<"  "<<endl;
cout <<"Press return to end . . ."<<endl; 
cin.get();	  	
}


  

I am new to C++, so don't put too much weight on my method.
That method will work if the member variables of the struct/class doesn't use dynamic memory (ie no string, no vector, no map etc etc). You have what's called fixed sized records which can be easily read/written from/to a file. But as per previous posts, the issue becomes when dynamic memory is used.
Ok, I see. So, since strings have no fixed length, the size of the a class object containing strings can vary between instances. Right? And the same problem with vectors of course.

Can anyone point me to a good tutorial on how to save and load? My current book only touches on it very superficially. But it seems I need to read up on the subject if I want to keep going with my project.
Ok, I see. So, since strings have no fixed length, the size of the a class object containing strings can vary between instances. Right? And the same problem with vectors of course.


Yes.

Books only touch upon it superficially because of the nature of the beast!

As has been mentioned before, one way is to write the number of elements used before the elements. So if you were writing a string, then write the number of chars in the string (.size()), then each element of the string (char usually, but could be wchar_t or other types for unicode).

Start small and easy and expand. Maybe an array of struct that contains say a double, a string, a char and a string. Get this to work. Then try an array of that struct. Then a vector of that struct. Then add a vector to the struct etc etc.
seeplus wrote:
char usually, but could be wchar_t or other types for unicode

Char could also be use used for Unicode, using the UTF-8 encoding.
Topic archived. No new replies allowed.