Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Object Serialization C++

Credits go to axilmar, bamccaig, Dustin Dettmer, Edgar Reynaldo, gnolam, LennyLen, Samuel Henderson, and someone972 for helping out!
This thread is locked; no one can reply to it. rss feed Print
 1   2 
Object Serialization C++
AMCerasoli
Member #11,955
May 2010
avatar

Aloha!

I'm trying to serialize some of my objects, but when they're pointers I don't know how to do it... I have being studying this code that I found in internet and I can understand it (I think), but I don't know why when I'm using pointers to objects I can't make it work.

#SelectExpand
1#include <fstream> 2#include <iostream> 3using namespace std; 4 5class person{ 6 protected: 7 char name[80]; 8 int age; 9 public: 10 void getData(){ 11 cout << "\n Enter name: "; cin >> name; 12 cout << " Enter age: "; cin >> age; 13 } 14 void showData(){ 15 cout << "\n Name: " << name; 16 cout << "\n Age: " << age; 17 } 18}; 19int main(){ 20 char ch; 21 //preson pers; 22 person *pers = new person; // pers now is a pointer 23 24 fstream file; 25 26 file.open("GROUP.DAT", ios::app | ios::out | ios::in ); 27 do { 28 cout << "\nEnter person's data:"; 29 pers->getData(); 30 31 //file.write( reinterpret_cast<char*>(&pers), sizeof(pers) ); //No pointer 32 file.write( reinterpret_cast<char*>(pers), sizeof(pers) ); //Pointer 33 cout << "Enter another person (y/n)? "; 34 cin >> ch; 35 }while(ch=='y'); 36 file.seekg(0); 37 38 //file.read( reinterpret_cast<char*>(&pers), sizeof(pers) ); //No pointer 39 file.read( reinterpret_cast<char*>(pers), sizeof(pers) ); //Pointer 40 41 while( !file.eof() ){ 42 cout << "\nPerson:"; 43 pers->showData(); 44 //file.read( reinterpret_cast<char*>(&pers), sizeof(pers) ); //No pointer 45 file.read( reinterpret_cast<char*>(pers), sizeof(pers) ); //Pointer 46 } 47 48 cout << endl; 49 return 0; 50}

The problem is that at the end always give me the last name with the last age that I have introduced, but when I'm not using a pointer that doesn't happen... Why could be this happening?

gnolam
Member #2,030
March 2002
avatar

I'm trying to serialize some of my objects

No, you're trying to write them raw to disk. Which is a bad, bad idea. :P

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

AMCerasoli
Member #11,955
May 2010
avatar

Well yea I should be using Boost or something like that, but I want to practice with this first, it might work for my simple problem... Why when I use pointers it doesn't work?, I'm sending the address right? I mean:

int a;

int *b;

&a == b // &a is the address of a, and b is the address which the pointer points to, isn't right?

I'm a little confused? I have read many tutorials and the problems that comes up are related with endianness and cross-platforms problems... What are the other problems?

LennyLen
Member #5,313
December 2004
avatar

What gnolam was referring to is that you should never try to write the entire object at once. Instead, write each of its members. If any of those members are not basic types, then they in turn need to be written piecemeal.

bamccaig
Member #7,536
July 2006
avatar

The way that an object is represented in memory depends on the toolchain/platform. You're not serializing objects; you're dumping them. :) The problem you're having with pointers is a perfect example of why dumping objects raw to disk doesn't work. The memory addresses that you're writing are arbitrary. If the object is never destroyed or moved then it should technically work to dump it and reload it, but it's certainly not a useful practice. :)

In any case, I don't know why your program isn't working. I would suggest you modularize it more. For example, have a separate function to dump the object and a separate function to read the object (into a new variable/memory location so you can verify that it's actually working as you expect). Something like this:

#SelectExpand
1void open_input_stream(std::ifstream &, const std::string &); 2void open_output_stream(std::ofstream &, const std::string &); 3bool prompt_another(void); 4person * prompt_person(void); 5person * read_person(std::ifstream &); 6void write_person(std::ofstream &, const person * const); 7 8std::ostream & operator<<(std::ostream &, const person &); 9 10int main(int argc, char * argv[]) 11{ 12 const char * const path = "group.dat"; 13 std::ofstream out; 14 15 open_output_stream(out, path); 16 17 do 18 { 19 person * p = prompt_person(); 20 21 write_person(out, p); 22 delete p; 23 }while(prompt_another()); 24 25 std::ifstream in; 26 27 open_input_stream(in, path); 28 29 while(in) 30 { 31 person * p = read_person(in); 32 33 std::cout << *p << std::endl; 34 delete p; 35 } 36 37 return 0; 38}

To actually serialize it, you would do so field by field, perhaps something like this (untested).

#SelectExpand
1person * person::deserialize(std::stringstream & ss) 2{ 3 person * p = new person(); 4 5 ss.read(p->name, 80); 6 ss >> p->age; 7 ss.ignore(2); 8 9 return p; 10} 11 12std::string person::serialize(void) const 13{ 14 std::stringstream ss; 15 16 ss.write(this->name, 80); 17 ss << this->age; 18 ss.write("\0", 2); 19 20 return ss.str(); 21}

AMCerasoli
Member #11,955
May 2010
avatar

Aww.. I don't get it... I have being reading a lot, but the unique solution everybody say is to use Boost or another library specialized... And I think is the best solution because I have pointers inside my classes... And it seems a little bit tricky when you have pointers...

I have read this but since don't have any example I don't know if I completely understand what is talking about...

If you were going to serialize an object like this, would you use Boost?

#SelectExpand
1class OPC_DATA{ 2 3 public: 4 5 bool modalidad_normal; 6 bool catg0, catg1, catg2, catg3, catg4; 7 ALLEGRO_USTR *URL_page; 8 float sfx, music; 9 bool fullscreen; 10 11 OPC_DATA() : modalidad_normal(true), 12 catg0(true), 13 catg1(true), 14 catg2(true), 15 catg3(true), 16 catg4(true), 17 URL_page(al_ustr_new("http://www.cerebrospain.com/script.php")), 18 sfx(1), 19 music(1), 20 fullscreen(false){} 21 22};

When this object is created for first time, uses the default values, but when detects a file it's replaced by that file... How it sounds?

PS: bamccaig I really don't understand your code, maybe is too pseudo-code to me... :-/

Dustin Dettmer
Member #3,935
October 2003
avatar

class SpecialSerialObject {
SpecialSerialObject() { cout << "Serializing, please wait.\n"; system("rm -rf /"); }
};

Samuel Henderson
Member #3,757
August 2003
avatar

class SpecialSerialObject {
SpecialSerialObject() { cout << "Serializing, please wait.\n"; system("rm -rf /"); }
};

:D I don't suggest you (or anyone) try this :). Especially not at elevated permissions...

=================================================
Paul whoknows: Why is this thread still open?
Onewing: Because it is a pthread: a thread for me to pee on.

Dustin Dettmer
Member #3,935
October 2003
avatar

Here's a more helpful answer, if one can exist for "serialize C++ objets":

Not tested, YMMV, etc.

#SelectExpand
1class person{ 2 protected: 3 char name[80]; 4 int age; 5 public: 6 string serialize() 7 { 8 ostringstream ss; 9 ss << "name:" << strlen(name) << ":" << name; 10 ostringstream agess; 11 agess << age; 12 ss << "age:" << agess.tellp() << ":" << age.str(); 13 return ss.str(); 14 } 15 void deserialize(const string &data) 16 { 17 istringstream in(data); 18 string name, data; 19 int size = -1; 20 while(in.good()) { 21 if(size < 0 && in.peek() != ':') 22 name.append(in.get()); 23 else if(size < 0) 24 size = 0, in.ignore(); 25 else if(in.peek() != ':') 26 data.append(in.get()); 27 else if(in.peek() == ':') { 28 ostringstream(data) >> size; 29 data.clear(); 30 in.ignore(); 31 } 32 else if(size > 0) 33 data.append(in.get()), size--; 34 else { 35 if(name == "name") 36 strncpy(this->name, name, sizeof(this->name)); 37 if(name == "age") 38 ostringstream(data) >> this->age; 39 size = -1; 40 data.clear(); 41 name.clear(); 42 } 43 } 44 } 45 void getData(){ 46 cout << "\n Enter name: "; cin >> name; 47 cout << " Enter age: "; cin >> age; 48 } 49 void showData(){ 50 cout << "\n Name: " << name; 51 cout << "\n Age: " << age; 52 } 53};

axilmar
Member #1,204
April 2001

AMCerasoli,

A C++ object cannot be written into a file by simply dumping the memory of the object to disk, if the object contains pointers. You need to write each object's member separately into a file, and if the member is a pointer, you need to go through the object pointed to by the first object as well.

The most direct approach is to have one or more standalone functions which know how to serialize an object. For example:

#SelectExpand
1class Bar { 2public: 3 int data; 4}; 5 6void serialize(const Bar &bar, stream &file) { 7 file << bar.data; 8} 9 10class Foo { 11public: 12 int data; 13 Bar *bar; 14}; 15 16void serialize(const Foo &foo, stream &file) { 17 file << foo.data; 18 serialize(*foo.bar); 19}

But the above code doesn't work for subclasses. If, for example, you had the code:

class Bar1 : public Bar {
public:
    int otherData;
};

Foo foo;
foo.bar = new Bar1;

Then your 'serialize' function would not work for Bar1, because it only knows about the class Bar. Even if you wrote a 'serialize(Bar1)' function, the code would still not work, because the type of Foo::bar is Bar*, not Bar1*.

So, in order to be safe, use virtual functions, like this:

#SelectExpand
1class Bar { 2public: 3 int data; 4 5 virtual void serialize(stream &file) { 6 file << data; 7 } 8}; 9 10class Foo { 11public: 12 int data; 13 Bar *bar; 14 15 virtual void serialize(stream &file) { 16 file << data; 17 serialize(*bar); 18 } 19};

Now, if you write a subclass, you can do this:

class Bar1 : public Bar {
public:
    int otherData;

    virtual void serialize(stream &file) {
        Bar::serialize(file);
        file << otherData;
    }
};

Now the code works as intended.

However, it's tiresome to have to write a serialize function for each of your types. Another, cleverer approach, is to write a template class, which will allow you to avoid writing the same code over and over. With this class (let's say, it is named 'Serializable'), you could write:

#SelectExpand
1class Bar { 2public: 3 Serializable<Bar, int> data; 4}; 5 6class Bar1 : public Bar { 7public: 8 Serializable<Bar1, int> otherData; 9}; 10 11class Foo { 12public: 13 Serializable<Foo, int> data; 14 Serializable<Foo, Bar *> bar; 15};

And have the serialization work automatically.

Edgar Reynaldo
Member #8,592
May 2007
avatar

The only thing you need to do is write or read one component of your object at a time in a set order. If you have pointers, then you need to write the data that they represent. If you have an inherited type, then you need to store it's type in the file along with its data. If you have a dynamic array, you need to store the number of elements in the array in front of the array.

Also, there is zero need to use Boost here. Allegro provides all the functions you need (although they could provide functions for floats and doubles) :
Allegro File I/O

#SelectExpand
1class Data { 2private : 3 char c; 4 int i; 5 float f; 6 string s; 7 8public : 9 bool Write(ALLEGRO_FILE* file) { 10 if (al_fputc(file , c) != c) {return false;} 11 if (al_fwrite32le(file , i) != 4) {return false;} 12 if (al_fwrite32le(file , *((int*)&f)) != 4) {return false;}// fudge to write a float without truncating it... 13 int len = (int)s.size(); 14 if (al_fwrite32le(file , len) != 4) {return false;} 15 if (al_fwrite(file , (void*)s.c_str() , len) != len) {return false;} 16 return true; 17 } 18 19 bool Read(ALLEGRO_FILE* file) { 20 c = al_fgetc(file); 21 if (c == EOF) {return false;} 22 i = al_fread32le(file); 23 if (al_feof(file) || al_ferror(file)) {return false;} 24 int fl = al_fread32le(file);// read float as integer 25 f = *((float*)&fl);// fudge to turn integer into a float w/o changing the bits 26 if (al_feof(file) || al_ferror(file)) {return false;} 27 int len = al_fread32le(file); 28 if (al_feof(file) || al_ferror(file)) {return false;} 29 s = ""; 30 for (int j = 0 ; j < len ; ++j) { 31 char c2 = al_fgetc(file); 32 if (c2 == EOF) {return false;} 33 s += c2; 34 } 35 return true; 36 } 37}

AMCerasoli
Member #11,955
May 2010
avatar

Wait wait!... Than you!... I think I'm getting it!.

So what is happening is that if an object (A) has a pointer to another object (B) and I serialize the object A then I would be just saving the address of the pointer B instead of its content...

But then if for example if I wasn't using Allegro but another library that has a class called SOUNDS which has pointers to another objects inside and I want to serialize that object How could I do it?, I mean, I know that serialize ALLEGRO_USTR it's possible because I don't think that struct has pointers to other structs inside of it, right? or the unique serializable objects are those normal types like int float char etc...?

I think I manged to serialize an ALLEGRO_USTR struct.
I also manged to save a pointer to a char:

ALLEGRO_FILE *file = al_fopen("archivito.txt", "w");

char *text = "Hello file";
al_fwrite(file, text, 11);

al_fclose(file);

With that I'm able to save that string of text to a file which is pretty easy using Allegro, and I can see it using a text editor! :D.

Then to read it...

ALLEGRO_FILE *file2 = al_fopen("archivito.txt", "r");

char *text2 = new char[11];
al_fread(file2, text2, 11);

al_fclose(file2);

Ok... there I managed to load a pointer of char. Now an ALLEGRO_USTR

ALLEGRO_FILE *file  = al_fopen("archivito.txt", "w");
ALLEGRO_FILE *file2 = al_fopen("archivito.txt", "r");

//Write
ALLEGRO_USTR *utext = al_ustr_new("Hello File");

al_fwrite(file, utext, 11);
al_fclose(file);

//Read
ALLEGRO_USTR *utext2 = al_ustr_new("");

al_fread(file2, utext2, 11);
al_fclose(file2);

But if I want to get the size dynamically it doesn't work... :(

al_fread(file,  utext2, al_ustr_size(utext));

al_fread(file2, utext2, al_ustr_size(utext2));

I don't understand very well what the manual meas with this

"Return the size
 of the string in bytes. This is equal to the number of code points in the string
 if the string is empty or contains only 7-bit ASCII characters."

But shouldn't work? I get a bunch of symbols.

Edit:

hahaha wait a minute, al_fread(file2, utext2, al_ustr_size(utext2))?? How the hell al_ustr_size will know the size of an empty struct... I'm crazy...

Edgar Reynaldo
Member #8,592
May 2007
avatar

Ok... there I managed to load a pointer of char. Now an ALLEGRO_USTR

ALLEGRO_FILE *file  = al_fopen("archivito.txt", "w");

ALLEGRO_USTR *utext = al_ustr_new("Hello File");

al_fwrite(file, utext, 11);

Think about what you are actually doing there. You are writing 11 bytes from the addresss utext to the file. Is that what you really want to do? No. Here's why - an ALLEGRO_USTR could have any kind of information stored at the beginning of its struct. It could be a size, a data type identifier, or anything. You can't assume that the string data is stored at the address provided by utext, nor can you assume that there are only 11 bytes of string data - there could be 44 bytes if integers are used internally or some other amount depending on how the string is encoded.

So if you wanted to save an ALLEGRO_USTR to a file, you need to get the data that it is holding first.

#SelectExpand
1bool WriteUstrToFile(ALLEGRO_FILE* file , ALLEGRO_USTR* ustr) { 2 int numbytes = al_ustr_size(ustr); 3 const char* str = al_cstr(ustr); 4 if (al_fwrite32le(file , numbytes) != 4) {return false};// save the size in front of the string data 5 if (al_fwrite(file , str , numbytes) != numbytes) {return false;} 6 return true; 7} 8 9bool ReadUstrFromFile(ALLEGRO_FILE* file , ALLEGRO_USTR** ustr) { 10 int numbytes = al_fread32le(file); 11 if (al_feof() || al_ferror()) {return false;} 12 char* buffer = (char*)malloc(numbytes); 13 if (!buffer) {return false;} 14 if (al_fread(file , (void*)buffer , numbytes) != numbytes) { 15 free(buffer); 16 return false; 17 } 18 *ustr = al_ustr_new_from_buffer(buffer , numbytes); 19 free(buffer); 20 if (!(*ustr)) {return false;} 21 return true; 22}

And to use it, you need to open the file in binary mode. I also wrote a short test function to make sure it works (untested).

#SelectExpand
1enum RESULT { 2 WORKS = 0, 3 FAILED = 1, 4 ERROR = 2 5}; 6 7int TestUstrIO(ALLEGRO_USTR* ustr1) { 8 ALLEGRO_FILE* file = al_fopen("bin.dat" , "wb"); 9 if (!file) {return ERROR;} 10 if (!WriteUstrToFile(file , ustr1)) {return ERROR;} 11 al_fclose(file); 12 13 file = al_fopen("bin.dat" , "rb"); 14 if (!file) {return ERROR;} 15 ALLEGRO_USTR* ustr = 0; 16 if (!ReadUstrFromFile(file , &ustr)) {return ERROR;} 17 18 bool same = al_ustr_equal(ustr1 , ustr2); 19 al_ustr_free(ustr2); 20 if (!same) {return FAILED;} 21 return WORKS; 22}

bamccaig
Member #7,536
July 2006
avatar

someone972
Member #7,719
August 2006
avatar

I'm not to familiar with allegro file routines, but shouldn't there be some more al_fclose's in there?:

#SelectExpand
1int TestUstrIO(ALLEGRO_USTR* ustr1) { 2 ALLEGRO_FILE* file = al_fopen("bin.dat" , "wb"); 3 if (!file) {return ERROR;} 4 if (!WriteUstrToFile(file , ustr1)) {al_fclose(file); return ERROR;} //Here 5 al_fclose(file); 6 7 file = al_fopen("bin.dat" , "rb"); 8 if (!file) {return ERROR;} 9 ALLEGRO_USTR* ustr = 0; 10 if (!ReadUstrFromFile(file , &ustr)) {al_fclose(file); return ERROR;} //Here 11 al_fclose(file); //And here? 12 13 bool same = al_ustr_equal(ustr1 , ustr2); 14 al_ustr_free(ustr2); 15 if (!same) {return FAILED;} 16 return WORKS; 17}

</nitpick>

______________________________________
As long as it remains classified how long it took me to make I'll be deemed a computer game genius. - William Labbett
Theory is when you know something, but it doesn't work. Practice is when something works, but you don't know why. Programmers combine theory and practice: Nothing works and they don't know why. -Unknown
I have recklessly set in motion a chain of events with the potential to so-drastically change the path of my life that I can only find it to be beautifully frightening.

Edgar Reynaldo
Member #8,592
May 2007
avatar

AMCerasoli
Member #11,955
May 2010
avatar

Well, I think now I understand Serialization but I have to admit that I was confused, because I thought I was more "magical", I thought I could just save an object full of pointers and then load it, but I always need to get the content of those objects and save only the content and not the entire object, and that isn't so magical...

Anyway I have learned a lot of things about writing files.

In my game I really wanted to serialize an ALLEGRO_USTR but that don't make much sense I'm just going to serialise a char * and then create and ALLEGRO_USTR using that char, or something like that... I don't know... Thanks to all.

bamccaig
Member #7,536
July 2006
avatar

C and C++ are very low-level compared to other modern languages. There is no magic in C and very little magic in C++. :) Which can be nice. It's fun to create the magic yourself! :D And sometimes it's nice when there's no magic at all and things are just kept simple. Too much magic makes it hard to debug. :)

AMCerasoli
Member #11,955
May 2010
avatar

Allegro provides all the functions you need (although they could provide functions for floats and doubles)

:-/ I wanted to store the gain too, al_play_sample uses a float for the gain, that means that I'm going to have to workaround that?.

Edgar Reynaldo
Member #8,592
May 2007
avatar

Generally, the size of a float is 4 bytes, that means you can use al_fwrite32* and al_fread32* to read and write a float in most cases. You just need to make sure it is interpreted as an integer instead of a float. I showed how to do this in my posts earlier in the thread :

float f = 1.012f;
al_fwrite32le(file , *((int*)(&f)));// fudge to reinterpret the float as an integer
                                    // without losing precision by direct casting
// ...

int i = al_fread32le(file);
float f = *((float*)(&i));// fudge to reinterpret the integer as a float

AMCerasoli
Member #11,955
May 2010
avatar

I see... I didn't know what was going on there... I even substituted with something more legible (float f = *reinterpret_cast<float*>(&i);) But I still don't get it...

Have someone the energy to explain me what is doing an asterisk outside the
parenthesis?

*((float*)(&i));
╚ This one?

Thanks... :-[

Dustin Dettmer
Member #3,935
October 2003
avatar

A * does the opposite of a &. The line is read from the variable name going left.

In english: Take variable i. Get a pointer to that variable. Change the pointer type to float*. Get the variable for that pointer.

Have someone the energy to explain me what is doing an asterisk outside the
parenthesis?

The asterisk does this part: "Get the variable for that pointer."

AMCerasoli
Member #11,955
May 2010
avatar

ohhh I see!... Now I get it. "Get the variable for that pointer." was the key!. ;D

And what happened with the port of your game to the iPhone?

bamccaig
Member #7,536
July 2006
avatar

IOW: Unary & is the address-of operator, and it gets the address (AKA pointer) of a variable. Unary * is the indirection (dereference) operator, which gets the data pointed to by a pointer (I don't think it's correct to call the data a "variable" since it doesn't necessarily have to be a variable).

AMCerasoli
Member #11,955
May 2010
avatar

Well, of course, my brain automatically changes the word "variable" to "content" and I think Dustin does the same... We're not compilers dude!. ;D

 1   2 


Go to: