Writing object into binary file

How do you write a whole object into a binary file?

Lets say I have a class X, and this class holds 3 ints and a string, can I write it into the file as an OBJECT or do I have to make function to extract the 3 ints and string, then write them individually onto the file?
If by string you mean std::string, then you can't just write the whole object. That's because the string contains a pointer to the actual character data which is stored outside the object (on the heap). Imagine you write such an object to a file. Later you read it back in. By then, the pointer will no longer be valid, and the memory it points to will contain something quite different.

On the other hand, if by string you mean c-string which is just an array of characters ending with a null byte, them you could write and read the whole object.
Generally the way to write objects and read objects is the process of serialization- because of the reason Chervil mentioned and other complexities with data types.

When I need easy read/write access to variables I'll usually implement a json interface- here is a relatively complicated example of one of my json export functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
std::string element::getJson() const{
        static std::vector<std::function<void (const element&,std::ostream&)> > funcs = {
            [](const element& dt,std::ostream& ss){ss << "";},
            [](const element& dt,std::ostream& ss){ss << dt.value;},
            [](const element& dt,std::ostream& ss){ss << (*((int*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((double*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((long*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((long long*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((short*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((char*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((bool*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((ui8*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((ui16*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((ui32*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((unsigned long*)(&dt.ulval)));},
            [](const element& dt,std::ostream& ss){ss << (*((ui64*)(&dt.ulval)));}
            };
        std::stringstream ss;
        funcs[this->valtype](*this,ss);
        return ss.str();
    }


This may not be the best example because of how abstract it is, I've also left off quite a bit of the recursive bit- but generally converting your object to and from a string representation is quick and efficient. Alternatively if you only have primitive types you can write them directly to a data file. For work I had to do a lot of interaction with ancient data files which had static types. You can certainly convert an object made of primitives to a char stream and write the block of data directly.

Something like this:

1
2
3
4
5
struct rawclass{
    int primitiveInt;
    char primitiveString[55];
    bool primitiveBool;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    rawclass a;

    std::string tmpval = "This is a string";

    memset(a.primitiveString,0,sizeof(a.primitiveString));
    strcpy(a.primitiveString,tmpval.c_str());

    a.primitiveInt = 1;
    a.primitiveBool = true;

    fstream file("test.raw",ios::out|ios::binary);
    file.write((char*)&a,sizeof(rawclass));
    file.close();

    rawclass b;

    file.open("test.raw",ios::in|ios::binary);
    file.read((char*)&b,sizeof(rawclass));
    file.close();

    cout << "INT\n\tA:" << b.primitiveInt << "\n\tB:" << a.primitiveInt << "\n";
    cout << "STRING\n\tA:" << b.primitiveString << "\n\tB:" << a.primitiveString << "\n";
    cout << "BOOL\n\tA:" << b.primitiveBool << "\n\tB:" << a.primitiveBool << "\n";


The problem with your class is definitely the string, it makes the whole process much more complex. You can take a look at the Boost implementation of serialization http://www.boost.org/doc/libs/1_37_0/libs/serialization/doc/index.html
Ah I see, ok so this is an example:

1
2
3
4
5
6
7
class Example
{
private: 
int x;
int y;
string test;
};


So since a string is just a character array, when I write it do I have to treat it as one? So I would have to create a buffer which is the size of the string, then copy each char onto the file?
My code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Btree::writeFile(Part *node)
{
	ofstream r("binarySave.dat", ios::out | ios::binary);
	if (node)
	{
		return writeFile(node->left);
		r.write((char*)&node->id, sizeof(node->id));
		r.write((char*)&node->price, sizeof(node->price));
		r.write((char*)&node->count, sizeof(node->count));
		r.write((char*)&node->descrip, sizeof(node->descrip));
		return writeFile(node->right);
	}
	else
		r.close();
	return;

}


basically this is a binary search tree with 3 ints and a string, can someone please help me on where to start for the string?
No a std::string is not just a character array (see Chervil's first paragraph).


To write a std::string to a binary file you can first write the length of the string followed by the string data.

1
2
3
std::size_t length = str.length();
file.write(reinterpret_cast<char*>(&length), sizeof(length));
file.write(str.data(), length);

To read the string back from file you can read the length, resize the string and then read the string data.

1
2
3
4
std::size_t length;
file.read(reinterpret_cast<char*>(&length), sizeof(length));
str.resize(length);
file.read(&str[0], length);

Note that data() returns a const pointer so you can't use it when reading from the file because the read function needs to modify the string data. Instead &str[0] can be used. It returns a pointer to the first character in the string, just like data() except that the pointer is not const.


Another way to write a string to a binary file is to simply write it using the stream operator << followed by some end of string marker.

 
file << str << '\0';

To read the string back from file you can use the getline function and pass the end of string marker as the delimiter.

 
std::getline(file, str, '\0');

Here I used the null character '\0' as the end of string marker. It is possible to use any other character as long as you are sure that the string will not contain the character.
Last edited on
Topic archived. No new replies allowed.