Jarek Danielak
Jarek Danielak

Reputation: 430

Saving vector of objects to file in C++

I want to save a vector of objects into file. What is the most efficient way to do it? Should I load the whole vector when the program starts, operate on it locally and then save it as the program exits or access the file every time I need to change something inside vector?

Also, is it even possible to save the whole vector at once or I need to save elements one by one?

Upvotes: 0

Views: 2167

Answers (2)

2785528
2785528

Reputation: 5566

If your data is POD (and thus each element is the same size), you can use the following ideas. If not, the following ideas might betoo difficult to use easily.

The following code shows that you 'can' use "ostream::write" to put the binary data out to a file. vector<> promises that the objects are front-to-back (or back-to-front, as you wish) in memory, so they are lined up and relatively compact for transfer.

Reading back in to memory is similar, using "istream::read". But here you need to allocate the buffering 1st.

In the following code, I allocated 6 objects for write, then found the beginning of the vector memory and wrote all 6 objects in with one 'write'.

For the read, I instantiated and loaded 6 objects into a new vector (note that vector provides a slicker way to allocate and instantiate the objects using the default ctor).

How you pass the number of objects from the write effort to the read? This can be computed from the file size. (see stat)

Using the reinterpret-cast is not (generally) acceptable, and may not be portable. But sometimes you just gotta do it.

Hopefully this 'evil' will prompt a number of better choices.

I left in some debug std::cout's ... hope you find them helpful.

edit - 4/8 -- cleaned the code somewhat

       // create a vector of 6 clear elements to save to file
       std::vector<Something>  vec6W(6);  // default ctor clears contents

       // change several elements by init'ing them
       vec6W[0].init();
       //   [1] is clear (all 0's)
       vec6W[2].init();
       vec6W[3].init();
       //   [4] is clear (all 0's)
       vec6W[5].init();

       std::cout << "                vec6W.size(): " << vec6W.size()      << std::endl; // 6
       std::cout << "               sizeof(vec6W): " << sizeof(vec6W)     << std::endl; // 12
       std::cout << "           sizeof(something): " << sizeof(Something) << std::endl; // 100
       std::cout << "            sizeof(vec6W[3]): " << sizeof(vec6W[3])  << std::endl; // 100

       //                        #elements      bytes per element
       std::streamsize nBytes = (vec6W.size() * sizeof(Something));
       std::cout << "                     nBytes : " << nBytes  << std::endl;  // 600

       // simulate a file using
       std::stringstream ss;

       // note size
       std::cout << "             ss.str().size(): " << ss.str().size() << std::endl;    // 0

       // identify address of 1st element of vector
       // cast to a 'const char*' for 'write()' method
       const char* wBuff = reinterpret_cast<const char*>(&vec6W[0]);

       // report address
       std::cout << " static_cast<const void*>(wBuff): " << std::hex
                 <<   static_cast<const void*>(wBuff)    << std::dec << std::endl;

       //write vector to ss
       ss.write(wBuff, nBytes);

       std::cout << "\n  note ss content size change " << std::endl;
       std::cout << "            ss.str().size() : " << ss.str().size() << std::endl;

       // //////////////////////////////////////////////////////////////////
       // build a vector of 6 clear elements to create buffer for read from file
       std::vector<Something>  vec6R(6);

       // identify address of 1st element of vector
       std::cout << "                  &vec6R[0] : " << std::hex <<  (&vec6R[0]) << std::dec  << std::endl;

       char* rBuff = reinterpret_cast<char*>(&vec6R[0]);

       // read vector from ss
       (void)ss.read(rBuff, nBytes); // read back same number of bytes

       // //////////////////////////////////////////////////////////////////
       // confirm vec6R matches what was written vec6W
       int diff = memcmp(wBuff, rBuff, nBytes);

       std::cout << (diff ? "FAILED" : "wBuff MATCHES rBuff: SUCCESS") << std::endl;

       // now consider comparing vec6R to vec6W element by element

There may be additional issues when the class has virtual methods.

Good luck.

edit -----

Pointers can be handled, but create additional work, and some asymmetry.

Related work might be called "persistant storage".

Also, there exists tools to simplify the additional steps of non-POD data (sorry, I have forgotten the name.)

Upvotes: 1

Peter
Peter

Reputation: 36597

There is no single answer to this question.

An appropriate approach depends on the needs of your application, why it is saving the file, and what will be done with the file. For example, a file that is intended to opened in another program and understood by a human may be written very differently from a file that just saves program state (i.e. that only a software program needs to make sense of it).

The most efficient depends on your measure of efficiency. Some possible measures include speed of writing, speed of reading, file size, size of code to do the writing, etc etc. Not all of these things go together - for example, an archiver program may choose a slow approach to writing a file, in order to achieve fast read speeds.

Usually, writing a collection of objects involves writing all the objects individually, plus some additional book-keeping (e.g. output the number of objects first), particularly if the file needs to be read later. However, smarter algorithms might derive some sort of summary data from a set of objects. For example, assume a vector containing the integers 1 to 20 in order. One way of writing is to write all 20 values. Another is simply to emit the string "1-20".

Upvotes: 1

Related Questions