• Advertisement
Sign in to follow this  

[SOLVED]streaming object to file (C++)

This topic is 3614 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

Internet searches say that I should do it like this:
void jSaveObj2D(jObject2D Obj2D, char *fileName)
{
	std::ofstream file(fileName, std::ios::binary);
	file.write((char *)(&Obj2D), sizeof(jObject2D));
	file.close();
}

void jLoadObj2D(jObject2D Obj2D, char *fileName)
{
	std::ifstream file(fileName, std::ios::binary);
	if(!file)
	{
		std::cout << "\nfile not found..";
	}
	else
	{
		file.read((char *)(&Obj2D), sizeof(jObject2D));
	}
	file.close();
}



but it doesn't seem to work.. I'm not sure why.. Does it matter if that structure contains other structures..? Example:
struct jVector2D {
    float x, y;
};

class jObject2D {
public:
    std::vector<jVector2D> Vertices;
    translate(float x, float y);
private:
    jVector2D position;
};



How can I stream I/O jObject2D to/from file..? [Edited by - experiment on March 27, 2008 1:26:37 PM]

Share this post


Link to post
Share on other sites
Advertisement
A naive bitwise approach like that isn't going to work except in the simplest of cases, and it's not guaranteed to work even then. It's simply bad code. Real-world serialization is a nontrivial problem with no simple solutions in the general case. A couple links you should definitely familiarize yourself with:

C++ FAQ Lite: Serialization

Boost Serialization library

Share this post


Link to post
Share on other sites
I don't want to familiarize myself with serialization. I just want to know how to store a -jObject2D- into binary (from the example i gave before)..

PS: That explanation link didn't help me..

Share this post


Link to post
Share on other sites
Quote:
I just want to know how to store a -jObject2D- into binary (from the example i gave before).

You can't just dump out/slurp up a non-trivial structure in the manner you are attempting. You will have to serialize your structure properly. All this means is you will have to do some additional work (beyond just file.write(struct) ). The simplest solution is to write out your structure member by member, and then read it back in the same manner. When it comes to arrays/vectors/lists, you may also need to store additional meta information such as the length of the array, so that you can pre-allocate space before reading the data back in, and so that you will know how much of the following data in the file belongs to that array. For your case, you could store the length of the Vertices vector, then each jVector2D in the vector as a float float pair. Then store position as a float float pair. Then when you read it back in, you know that the first value is the number of pairs of floats to read to populate your Vertices array, and then the final float pair is assigned to "position".

Quote:
I don't want to familiarize myself with serialization.

Oh, sorry then. Unfortunately, there is no general solution for what you are trying to do. The problem is solved using serialization. I was under the impression that you wanted a solution to your problem. If that is not the case, why did you even post your question?

[Edited by - CodeMunkie on March 25, 2008 11:32:27 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by experiment
I don't want to familiarize myself with serialization. I just want to know how to store a -jObject2D- into binary (from the example i gave before)..


Unlike most other languages, C++ provides no serialization system for its objects. As such, you are faced with three alternatives:

  1. Find a third party serialization system, learn how to use it, and apply it to your specific problem. Boost.Serialization is an example of such a system.
  2. Learn how to serialize objects without third party libraries, and do it yourself. This might be shorter on simple cases, but will be longer and more error-prone if you have to work with many different object types.
  3. Use a different language. For instance, Java, Objective Caml and all .NET languages (C#, VB.NET) provide automatic serialization through a single function call


The solution you provided involves doing (char *)(&Obj2D). This examines the bytes that are present at the memory location of your object, which:
  • Is only a small part of the object data, since the object may be spread over several locations through use of pointers.
  • Depends on the current memory layout of the program. That is, the same bytes used in another executable (or in another run of the same executable, or even the same run of the same executable a few seconds later) will represent a different object, which will most of the time be invalid.


The only situation where the memory layout of an object is a correct and serializable representation of that object, is when the object is of a POD type: either a basic non-pointer type (integers or floating-point numbers, mostly) or a structure type which contains no constructors, no destructors, no base classes, and no non-POD members. As soon as you include a non-POD member (such as std::vector) the memory layout of your object ceases to be a complete and persistent representation of its state, and thus cannot be used to save the object to a file reliably.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
The only situation where the memory layout of an object is a correct and serializable representation of that object, is when the object is of a POD type: either a basic non-pointer type (integers or floating-point numbers, mostly) or a structure type which contains no constructors, no destructors, no base classes, and no non-POD members. As soon as you include a non-POD member (such as std::vector) the memory layout of your object ceases to be a complete and persistent representation of its state, and thus cannot be used to save the object to a file reliably.


It's even worse than that, of course. If a compound POD type contains a pointer member, it's probably not correct to write out objects of that type byte-for-byte.

You also have padding issues to overcome. So you really do need to look in to serialization solutions, whether you want to or not. C++ just doesn't have enough introspection support to do this for you.

Share this post


Link to post
Share on other sites
Quote:
Original post by experiment
I don't want to familiarize myself with serialization. I just want to know how to store a -jObject2D- into binary (from the example i gave before)..


"I don't want to familiarize myself with driving. I just want to know how to get my car from point A to point B."

Share this post


Link to post
Share on other sites
The analogy with driving was funny, although it'd be more like:
"I don't want to familiarize myself with driving. I just want someone to get my car from point A to point B for me."
Which means: I was just hoping someone would easily write a serialization example code (based on example I gave). But I guess serialization in C++ isn't so straightforward as I thought.. I knew there'd be crap with std::vector..

But thanks everyone for taking your time to explain this. I'll try the Boost serialization library..

Share this post


Link to post
Share on other sites
Quote:
Original post by experiment
The analogy with driving was funny, although it'd be more like:
"I don't want to familiarize myself with driving. I just want someone to get my car from point A to point B for me."
Which means: I was just hoping someone would easily write a serialization example code (based on example I gave). But I guess serialization in C++ isn't so straightforward as I thought.. I knew there'd be crap with std::vector..


This statement has somewhat more far reaching implications than you'd imagine.

You're claiming you do not want to know anything at all about C++ memory layout and management, which means you'll be unable to develop anything non-trivial in C++.

When people say that C++ is difficult, complex and error prone, they do so for a reason.

Unless you clear these terms for yourself, you'll be running into many problems as soon as you start using pointers and references, and basic topics like shallow vs. deep copy, assignment operator, and other basic memory management.

Also: "file.close();" in the original example is not needed. It's quite important to understand why that is so.

Share this post


Link to post
Share on other sites
Quote:
Original post by experiment
The analogy with driving was funny, although it'd be more like:
"I don't want to familiarize myself with driving. I just want someone to get my car from point A to point B for me."
Which means: I was just hoping someone would easily write a serialization example code (based on example I gave). But I guess serialization in C++ isn't so straightforward as I thought.. I knew there'd be crap with std::vector..

But thanks everyone for taking your time to explain this. I'll try the Boost serialization library..


I outlined exactly what you need to do in my previous post.

Share this post


Link to post
Share on other sites
Oh dammit.. Not only I can't figure out myself how to serialize, but even Boost library can't help me.. shoot..

I'm officially not a programmer.. I was just carried into my little C++ openGL adventure. Doing it just for fun.. Yeah, I think it's fun.. Well, as long as you learn only what you want, that is. But all this small clusters of obstacles seem to take most of the time. They take away the fun factor. So instead of dealing with them myself, I thought I could just ask someone who already knows.. Like GameDev guys, right.? :) But it seems I was wrong.. There is no way around the problem.

Guess I'm just stupid for this stuff, and that isn't fun.

Share this post


Link to post
Share on other sites
Quote:
Original post by CodeMunkie
I outlined exactly what you need to do in my previous post.


Yeah, maybe I don't know how to do what you said.. How about that..?

I mean, I'd know how to write vector of floats in: size_n, f1, f2, fn.. but how the heck can I reverse that..? (read, in other words) Especially if I want it in binary.. I don't get it..

Share this post


Link to post
Share on other sites
Quote:
CodeMunkie
The simplest solution is to write out your structure member by member, and then read it back in the same manner.


There ya go.

edit: you updated your post.

Quote:
I mean, I'd know how to write vector of floats in: size_n, f1, f2, fn.. but how the heck can I reverse that..? (read, in other words) Especially if I want it in binary.. I don't get it..


You have to make certain guarantees about the structure of your file.

1)The first four bytes are an unsigned integer specifying N, the number of floats that follow
2)The next N elements are each sizeof(float)==8 bytes
3)After all of the floats you have a four byte unsigned integer specifying X, the number of ints that follow
4)The next Y elements are each sizeof(int)==4 bytes

More advanced file formats tend to have headers that describe the information that follow and then the raw data. Certain structures place a header at the end of the file (in case you want to append more data to the file later).

Reading it is reverse. You know based on your requirements that when you start reading data it will say the number of elements to read and that the elements you are going to read are floats. And that after the floats you read the number of ints to read and then you read the ints.

The next step is to do this recursively.
struct B {
std::vector<floats> fB;
std::vector<ints> iB;
};

struct C {
float fC1;
float fC2;
};

struct A {
std::vector<B> b;
unsigned int uintA1
unsigned int uintA2
C c;
};


Start at the highest level, A. To write an A you need to write a Vector of Bs, UINT, UINT, and a C. Let us assume we have functions for this then your code would look like this:

writeA(const A &a) {
writeVecOfBs(a.b);
writeUINT(a.uintA1);
writeUINT(a.uintA2);
writeC(a.c);
}


Let's say that writeUINT and writeFLOAT are primitives that take unsigned ints and floats respectively and write them to file. All other write* functions are built on top of them. So how do we make writeVecOfBs?

writeVecOfBs(const std::vector<B> &b) {
writeUINT(b.size());
for(unsigned int i = 0; i < b.size(); ++i)
{
writeB(b);
}
}


Now we just need to make writeB and writeC. You should start to see a bunch of patterns in this process. In fact, you can automate much of this with templates (and the serialization libraries mentioned help with this). But now the question is "how do I read this back?"

Well, exactly the same way. We recursively read the data until we get to primitives.

readA(const A &a) {
readVecOfBs(a.b);
readUINT(a.uintA1);
readUINT(a.uintA2);
readC(a.c);
}

readVecOfBs(std::vector<B> &b) {
unsigned int size;
readUINT(size);
B temp;
for(unsigned int i = 0; i < size(); ++i)
{
readB(temp);
b.push_back(temp);
}
}


[Edited by - nobodynews on March 27, 2008 11:34:36 AM]

Share this post


Link to post
Share on other sites
The documentation for boost::serialization isn't the best, but its rather simple to use, once you actually know how to use it.


#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <fstream>
#include <string>

struct jVector2D {
float x, y;

//This template function allows us to read and write with a single function
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
//ar is the archive we want to read/write to/from.
//operator& is overloaded, so we can simply write the following
//And have it have the expected behavior.
ar & x;
ar & y;
}
};

class jObject2D {
public:
std::vector<jVector2D> Vertices;
translate(float x, float y);

//std::string is superior to char arrays for representing string data.
void save(const std::string& filename)
{
//Open the file in binary format.
std::ofstream outStream(filename.c_str(), std::ios_base::out | std::ios_base::binary);
if (outStream) //Check for valid stream
{
//Create a binary out archive we can use to archive our stuff.
boost::archive::binary_oarchive archive(outStream);
//Boost is smart enough to archive a vector without trouble.
archive & Vertices;
archive & position;

//Technically uneeded, but I like it here anyway.
outStream.close();
}
}

//This loads the stuff for us.
void load(const std::string& filename)
{
//Open the stream in binary format.
std::ifstream inStream(filename.c_str(), std::ios_base::in | std::ios_base::binary);
if (inStream)
{
//Create a binary in archive we can use to unarchive our stuff.
boost::archive::binary_iarchive archive(inStream);
//Once again, its trivial to archive a vector.
archive & Vertices;
archive & position;

//Techincally uneeded, but I like it here, as above.
inStream.close();
}
}
private:
jVector2D position;
};


Share this post


Link to post
Share on other sites
Yes, that's it..! It works!

Special thanks:
nobodynews and
bobofjoe

Both methods work perfectly fine, and were extremely helpful. I went with one-by-one style though.. I just like it when there is less alien code :P

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement