1

I m trying to save all the member variables of an object in a binary file. However, the member variables are vectors that is dynamically allocated. So, is there any way to combine all the data and save it in a binary file. As of now, it just saves the pointer, which is of little help. Following is my running code.

#include <vector>
#include <iostream>
#include <fstream>

class BaseSaveFile {

protected:
    std::vector<float> first_vector;

public:
    void fill_vector(std::vector<float> fill) {
        first_vector = fill;
    }

    void show_vector() {
        for ( auto x: first_vector )
        std::cout << x << std::endl;
    }

};


class DerivedSaveFile : public BaseSaveFile {


};


int main ( int argc, char **argv) {

    DerivedSaveFile derived;
    std::vector<float> fill;
    for ( auto i = 0; i < 10; i++) {
        fill.push_back(i);
    }
    derived.fill_vector(fill);
    derived.show_vector();

    std::ofstream save_object("../save_object.bin", std::ios::out | std::ios::binary);
    save_object.write((char*)&derived, sizeof(derived));

}

Currently size of the binary file is just 24 bytes. But I was execting much larger because of the vector of 10 floats.

infoclogged
  • 3,641
  • 5
  • 32
  • 53
  • 2
    Possible duplicate of [How do you serialize an object in C++?](https://stackoverflow.com/questions/523872/how-do-you-serialize-an-object-in-c) – Max Vollmer Sep 01 '18 at 15:27

2 Answers2

3

"is there any way to combine all the data and save it in a binary file" - of course there is. You write code to iterate over all the data and convert it into a form suitable for writing to a file (that you know how to later parse when reading it back in). Then you write code to read the file, parse it into meaningful variables classes and construct new objects from the read-in data. There's no built-in facility for it, but it's not rocket science - just a bunch of work/code you need to do.

It's called serialisation/de-serialisation btw, in case you want to use your preferred search engine to look up more details.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • ah i see... so I have to iterate over each member variable one by one. So, in the above case, first_vector needs to explicitly iterated? – infoclogged Sep 01 '18 at 14:20
  • thanks for the hint about serialisation.. I will look it up. – infoclogged Sep 01 '18 at 14:23
  • You cannot iterate over members of a class in C++ - you have to know about what members are there. But yes, for containers you need to iterate their elements and do something sensible with them that you can reverse later. – Jesper Juhl Sep 01 '18 at 14:23
  • @infoclogged: for your simple case, a simple `write(fill.data(), fill.size()*sizeof(float));` does the job. – geza Sep 01 '18 at 14:24
  • 1
    i found the relevant post - https://stackoverflow.com/questions/523872/how-do-you-serialize-an-object-in-c. If you want you can mark this as its duplicate. – infoclogged Sep 01 '18 at 14:35
0

The problem

You can write the exact binary content of an object to a file:

save_object.write((char*)&derived, sizeof(derived));

However, it is not guaranteed that you you read it back into memory with the reverse read operation. This is only possible for a small subset of objects that have a trivially copyable type and do not contain any pointer.

You can verify if your type matches this definition with std::is_trivially_copyable<BaseSaveFile>::value but I can already tell you that it's not because of the vector.

To simplify a bit the formal definition, trivially copyable types are more or less the types that are composed only of other trivially copiable elements and very elementary data types such as int, float, char, or fixed-size arrays.

The solution: introduction to serialization

The general solution, as mentionned int he other response it called serialization. But for a more tailored answer, here is how it would look like.

You would add the following public method to your type:

std::ostream& save(std::ostream& os){
    size_t vsize=first_vector.size(); 
    os.write((char*)&vsize, sizeof(vsize));
    os.write((char*)first_vector.data(), vsize*sizeof(float)); 
    return os; 
}

This method has access to all the members and can write them to the disk. For the case of the vector, you'd first write down its size (so that you know how big it is when you'll read the file later on).

You would then add the reverse method:

std::istream& load(std::istream& is){
    size_t vsize; 
    if(is.read((char*)&vsize, sizeof(vsize))) {
        first_vector.resize(vsize);
        is.read((char*)first_vector.data(), vsize*sizeof(float)); 
    }
    return is; 
}

Here the trick is to first read the size of the vector on disk, and then resize the vector before loading it.

Note the use of istream and ostream. This allows you to store the data on a file, but you could use any other kind of stream such as in memory string stream if you want.

Here a full online example (it uses stringstream because the online service doesn't provide for files to be written).

More serialization ?

There are some serialization tricks to know. First, if you have derived types, you'd need to make load() and save() virtual and provide the derived types with their own overridden version.

If one of your data member is not trivially copyable, it would need its own load() and save() that you could then invoke recursively. Or you'd need to handle the thing yourself, which is only possible if you can access all the members you'd need to restore its state.

Finally, you don't need to reinvent the wheel. There are some libraries outside that may help, like boost serialisation or cereal

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Nitpick: It is possible that `std::vector` is composed of only trivially copyable members. What it makes it non-trivially-copiable is that for example, it has a user-defined copy constructor. – geza Sep 01 '18 at 16:38
  • @geza yes, you are of course right. A pointer is a scalar and as such is trivially copyable but writing and reading it on disk would of course not work. I didn't bother to mention this because in principle, well behaving classes with pointer members should respect the rule of 3 and should therefore not be trivially copyable. But thank you for pointing this out: I've edited to clarify. – Christophe Sep 01 '18 at 17:05
  • Sorry to nitpick again :) A pointer member doesn't necessary mean that it owns the pointed memory area. An object could have a pointer member, and yet trivally-copyable. For example, `std::string_view` can/should be trivially-copyable, and yet contains a pointer. – geza Sep 01 '18 at 17:27
  • @geza fortunately my revised wording (following your previous remark) already eliminated this case: "*a trivially copyable type and do not contain any pointer*". But you pinpoint here an interesting point which is beyond the scope of the question: if there's a pointer to an object that is not owned, you'd need to think twice about the serialization. Worse if you have two objects pointing at each other. – Christophe Sep 01 '18 at 18:53