Binary File I/O Template Function

I've been trying now for the past few days to write a template function allowing me to save and later load an entire object. I have failed almost succeeded...

well, here's my code on the functions, main and the class I am using for saving/loading.

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <iostream>
#include <fstream>
#include <sys/stat.h>

using namespace std;

bool FileExists(std::string File)
{
    struct stat FileData;
    bool Exists=false;
    int FileInfo;
    FileInfo = stat(File.c_str(),&FileData);
    if(!FileInfo)Exists=true;
    return(Exists);
}

/**
To make it not use pointers remove the * after T and add a & after (char*).
*/

//SaveObject prototype

template <typename T>
void SaveObject(T* object,std::string filename);

//SaveObject defined

template <typename T>
void SaveObject(T* object,std::string filename)
{
    ofstream out(filename.c_str(),ios::binary);
    out.write((char *)object, sizeof(T));
    out.close();
}

//LoadObject prototype

template <typename T>
bool LoadObject(T* object,std::string filename);

//LoadObject defined

template <typename T>
bool LoadObject(T* object,std::string filename)
{
    bool Exists=FileExists(filename);
    if(Exists)
    {
        ifstream in(filename.c_str(),ios::binary);
        if(in.is_open())
        {
            in.read((char *)object,sizeof(T));
            in.close();
        }
    }
    else
    {
        cout<<"Unable to find \""<<filename<<"\"!"<<endl;
    }
    return Exists;
}

//Test class


class Player
{
public:
    int Level;
    int Strength;
    int Wisdom;
    float Health;
    float Mana;
    float Armor;
    string Class;
    string Race;
    string Name;

    Player() {}
    void Say()
    {
        cout<<Name<<" a level "<<Level<<" "<<Race<<" "<<Class<<endl;
        cout<<"HP: "<<Health<<'\t'<<"MP: "<<Mana<<'\t'<<"AC: "<<Armor<<endl;
        cout<<"Strength: "<<Strength<<'\t'<<"Wisdom: "<<Wisdom<<endl;
    }
};

int main()
{
    //Create some test objects of our class.
    Player p1;
    p1.Armor=5;
    p1.Class="Programmer";
    p1.Health=12.5;
    p1.Level=99;
    p1.Mana=7.77;
    p1.Name="IGnatus";
    p1.Race="Extra Ordinary";
    p1.Strength=5;
    p1.Wisdom=100;

    p1.Say();

    cout<<"---------------"<<endl;

    Player p2;

    //Save p1's data, not the values.

    SaveObject<Player>(&p1,"Player1.bin");

    //Load data of p1 into p2.

    if(LoadObject<Player>(&p2,"Player1.bin"))
        p2.Say();//if all goes well, show it's values.

    return 0;
}

resulting in;
IGnatus a level 99 Extra Ordinary Programmer
HP: 12.5        MP: 7.77        AC: 5
Strength: 5     Wisdom: 100
---------------
IGnatus a level 99 Extra Ordinary Programmer
HP: 12.5        MP: 7.77        AC: 5
Strength: 5     Wisdom: 100


This is not finished! I have severe lag after i load and display the second Player, and it will last until I kill it. This doesn't happen if I run it again without compiling.

Help would (still) be much appreciated!
Last edited on
In Save fstream out(filename.c_str(),ios::binary); fstream does not create a file if it does not exist (I think that's system dependent) . You may want to use ofstream
The same goes for Load, use ifstream instead.

Also (same for Save)
1
2
3
bool LoadObject(T* object,std::string filename) //It receives a pointer
  //in.read((char *) &object,sizeof(T)); //you are using the memory address of the pointer!
  in.read( (char *) object, sizeof(T) );
Last edited on
Thanks, had a friend look at my code, also if you look closely i forgot <type> when calling the functions.

Updating with my working program showing it's use for more complicated classes.

Sometimes the program takes forever to end. Ii guess that's my new issue
Last edited on
string is a dynamic object, it contains a pointer (try cout<<sizeof(string)). So the data written in the file is useless.

In order to write it properly you should write its content file.write( s.c_str(), /*s.size()+1*/ ); However then the registers will not have the same size

Besides when you write to the other string, you have the same pointers in p1 and p2 that will be freed twice (segmentation fault). So you need to change the Load. Maybe use an auxiliary
1
2
3
char aux[size];
file.read( aux, size ); //be careful to include the '\0'
s = aux;
Last edited on
Hey, I didn't read the whole thing, just ne555's last comment about the register length being disrupted by variable string length.

If you are trying to get rid of that, I would do the following:

1
2
3
4
5
struct SerializableString
{
    char *theString;
    unsigned int stringOffset;
}


I would then create an array of SerializableString and fill it in a loop. For the example code, let's imagine that arrStrings is a vector<std::string> (or std::wstring) that contains all the strings from all registers. Or better yet, a vector<char *> so we don't waste RAM duplicating the strings. Let's imagine that the original strings live safely elsewhere in string objects (this is the same reasoning for using char * in SerializableString):

1
2
3
4
5
6
7
8
9
10
//Since I know the length of the array, I'll just use a C array here:
SerializableString *arrIndexes = new SerializableString[arrStrings.size()];
//The byte counter.  Remember that 1 char = 2 bytes if using unicode!
unsigned int totalBytes = 0;
for (vector<char *>::iterator it = arrStrings.begin(); it < arrStrings.end(); it++)
{
    arrIndexes[it - arrStrings.begin()].theString = *it;
    arrIndexes[it - arrStrings.begin()].stringOffset = totalBytes;
    totalBytes += strlen(*it) + 1; //Must account for the null char.
}


Now, instead of saving the string with the individual register, you just save the string offset stored in arrIndexes. Note that arrIndexes contains a reference to the string, but maybe it is not needed. I added it just in case.

Save at the very beginning of the file the total number of records. Then offset 0 is the next byte after the last record, which should be of a constant size because any and all strings are saved after them. Oh, and yes, save the strings including their null chars at the end, one after another. :-)

Sorry if this is irrelevant to the post. The topic caught my eye but in the end I didn't fully read it.

And now that I'm done writing, I see that arrStrings is unnecessary. Instead, use a vector<SerializableString>. After you have constructed it (added all strings), calculate the offsets.
This isn't about saving strings, but being able to convenient save and load binary files for a given file and class. Not proper ways of preparing such class.

I will make a 'tutorial' about such later, i also know about variable string lengths however saving a small string seams to work, will test with larger strings.
Topic archived. No new replies allowed.