What's the suggested way of reading from/writing to a file?

I have a simple program that manages collections of items by doing a few CRUD operations. For now, it only stores into memory, in 3 vectors of structs.
I'm making a few functions that store those vectors into simple txt files and then reads from them. I have a file for each vector.
But I don't know when it's best to call those functions..

My code is organized as follows:
* main function declares 3 vectors of structs (one for each collection).
* Then it shows the user a menu offering CRUD options (add an item to a collection, modify an item, delete an item, view listings)
* Depending on user choice, functions are called that perform the specified operation (these functions might call some more functions, since I tried to make my modules as atomic and readable as possible).

And This is what my file I/O functions do:
void serialize_item(const string& file_name, t_item& an_item)
Declares a ofstream variable and appends (ios::app) each item field to the txt file. Then calls file.close()

void serialize_all_items(const string& file_name, vector<t_item>& items)
Declares a ofstream variable and overwrites (ios::trunc) file contents. Iterates through vector, writing each item field to the txt file. Then calls file.close()

void deserialize_items(const string& file_name, vector<t_item>& items)
Declares a local t_item variable and a ifstream variable. Iterates reading the file contents and saving each item field to the t_item variable. When an item is complete, it calls push_back to save it into the vector and goes back to reading the next item from file. Then calls file.close()


I have 3 functions like those for each vector type and I intend to move them to a different .cpp file for clarity and some sort of encapsulation.

First question: Should I open the file when the program starts and then close it when it finishes? (I thought that wouldn't be the best choice).

Another question: Every time I call a function for a CRUD operation I first load the items from the file into a vector, do whatever it's needed and then save vector contents to the file.
Let's say the "update an item" operation: Load the whole file contents to a vector, find the element to be updated (by iterating the vector, but now it's just a memory operation) then update it. Once it's done, I call the serialize_all_items function to re-write the file.

Is this a good practice? Am I missing something?

Thanks!
Last edited on
First Question: I think it is safer to open the file when you need it, make your changes, then close it.

Another Question: If the number of elements is not large, your approach is fine; however, it will be more beneficial to have a way to deal with a large number of records:

1
2
3
4
5
6
7
8
9
10
11
12

The user decides to do an update to an item.

    1a: Calculate the likely position of that record
   or
    1 b: Read all of the records until you find the desired record

    2: Update in memory

    3: Write back the updated memory into the file

I think it would help if t_item provided << and >> operators to serialize itself. This will allow you to save private/protected data. your current serialize_item() can only see public members.

1
2
3
4
5
6
7
ofs.open();

ofs << items.size();
for (auto item: items)
   ofs << item;

ofs.close();

Thanks for the info :)

Now I have one more (un)related question: how do I place my serialize/deserialize functions in a different file so I can just invoke them from my main program without having to copy and paste the functions code?
I mean, something like what I do when I include <vector> and just use the functions defined for a vector objetct.

Do I want to create a header file? A library? None?

Thanks!
Last edited on
Im not so sure you can just copy paste the serialize code, it will most likely be different for each class you want to serialize.

add 2 serialize methods to each class you want to serialize. istreams/ostreams work with bytes of data so you need to pass your data ass such. casting an address to char* is enough for native types. if you have other classes as members, then they will also need their own serialize code which will need calling.

remember, classes and structs are almost the same thing, so you can add these methods to structs also.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class AClass
{
   public:
      void serialize(ostream& out)
      {
         out.write((char *) &someData, sizeof(int));
      }

      void serialize(istream& in)
      {
         in.read((char *) &someData, sizeof(int));
      }

   protected:
      int someData;
}

class MyClass
{
   public:
      void serialize(ostream& out)
      {
         out.write((char *) &myData, sizeof(int));
         out.write((char *) &moreData, sizeof(float));
         otherData.serialize(out);
      }

      void serialize(istream& in)
      {
         in.read((char *) &myData, sizeof(int));
         in.read((char *) &moreData, sizeof(float));
         otherData.serialize(in);
      }

   protected:
      int myData;
      float moreData;
      AClass otherData;
}
Last edited on
Topic archived. No new replies allowed.