Write and read (binary) a vector containing class to a file.

Hi all!

So here is my question, I have a class that contains a vector that holds multiple vectors of char. I want to write & read that class to a file in binary. I *believe* I am correctly writing to the file, but when I go to read the file, the program crashes.

The program is semi large, but here are some code snippets. I can provide more if these are not helpful. THANK YOU in advance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 /// BEGIN FILE OUTPUT ///
  string directory = createDirectory( plateNumber );
  fstream outputFile( directory, ios::out | ios::binary );

  if( !outputFile )
  {
    cout << "ERROR: Could not create file." << endl;
    return false;
  }
  else
  {
    outputFile.seekg( 0 );
    outputFile.write( reinterpret_cast< char * >( &vehicle ), sizeof( Record ) );
    outputFile.close();
    cout << " - Record saved -" << endl;
    return true;
  }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 Record vehicle;
  string plateNumber;

  cout << "  >> VEHICLE SEARCH << " << endl;
  cout << "\n  Enter vehicle license plate number." << endl;
  cin >> plateNumber;

  /// BEGIN FILE INPUT ///
  string directory = createDirectory( plateNumber );
  fstream inputFile( directory, ios::out | ios::binary );

  if( !inputFile )
  {
    cout << "ERROR: Could not open file." << endl;
  }
  else
  {
    cout << "DEBUG: ENTERING else" << endl;
    inputFile.seekg( 0 );
    inputFile.read( reinterpret_cast< char * >( &vehicle ), sizeof( Record ) );
  }
  cout << "DEBUG: END" << endl;



This is some of the code that saves to the vector in vector.

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
void Record::addEoNumber( string disc )
{;
  EOnumber.clear();
  unsigned int length = disc.size();
  if( length > 0 )
  {
    for( size_t i = 0; i < length; ++i )
    {
      EOnumber.push_back( disc[ i ] );
    }
    EOnumber[ length ] = '\0';
    EOvector.push_back( EOnumber );
  }
}

string Record::getEoNumber( int number ) const
{
  if( getEoNumberCount() > 0 )
  {
    string stringReturn;
    unsigned int length = EOvector[ number ].size();
    for( size_t i = 0; i < length; ++i )
    {
      stringReturn += EOvector[ number ][ i ];
    }
    return stringReturn;
  }
}

Last edited on
Look at this in your "input" function:
 
fstream inputFile( directory, ios::out | ios::binary );

@tpb thanks for the error catch! I changed it to "ios::in" but I am still getting the same crash. :(
The problem is that you have a vector and/or string in Record. You can't just copy the bytes in the Record to the file since only a small amount of vector/string data is actually in the record (such as a pointer to the actual data, size, and capacity). The actual data of the vector/string is stored on the "heap" (i.e., it's dynamically allocated with new). So you need to go deeper to save all of the data.

I'll let you contemplate that for a while and I'll post some code later. (Gotta eat lunch and write the code first.)
Last edited on
Thanks @tpb! I had a feeling it had to do with how I was saving the object with internal vectors. I've used this method with built in char arrays, but not with vectors, let alone vectors in a vector lol! An help you could provide on how to process the vectors would be much appreciated!
Last edited on
Here's a simplified example. The record is two ints and a string. The idea is that you need to save the size of the string/vector and then the data.
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

struct Record {
    int a, b;
    string s;
    Record(int a, int b, string s) : a(a), b(b), s(s) {}
    Record() : a(), b() {}
};

bool output(Record vehicle) {
  string plateNumber("12345");
  ofstream out(plateNumber, ios::binary);
  if (!out)
    return false;

  out.write(reinterpret_cast<char*>(&vehicle.a), sizeof(int));
  out.write(reinterpret_cast<char*>(&vehicle.b), sizeof(int));

  // Write the size of the string.
  int size = vehicle.s.size();
  out.write(reinterpret_cast<char*>(&size), sizeof(int));

  // Write the string data.
  const char *p = vehicle.s.data();
  size_t size2 = vehicle.s.size();
  out.write(p, size2);

  return true;
}

bool input(Record &vehicle) {
  string plateNumber("12345");
  ifstream in(plateNumber, ios::binary);
  if (!in)
    return false;

  in.read(reinterpret_cast<char*>(&vehicle.a), sizeof(int));
  in.read(reinterpret_cast<char*>(&vehicle.b), sizeof(int));

  // Read in the size of the string.
  int size = 0;
  in.read(reinterpret_cast<char*>(&size), sizeof(int));

  // Read in the string.
  char str[1000];
  in.read(str, size);
  vehicle.s = str;

  return true;
}

int main() {
    Record v(1, 2, "hello");
    
    if (!output(v)) {
        cerr << "Error writing file.\n";
        return 1;
    }

    Record w;
    if (!input(w)) {
        cerr << "Error reading file.\n";
        return 1;
    }

    cout << w.a << ", " << w.b << ", " << w.s << '\n';

    return 0;
}


BTW, why are you using a vector<char> instead of a string?

Also, I wouldn't add a null char to the end. std::strings don't have that and you certainly don't need it in a vector<char>.
An example where the struct contains a vector of strings.
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

struct Record {
    int a, b;
    vector<string> v;
    Record() : a(), b() {}
    Record(int a, int b) : a(a), b(b) {}
    void add(string s) { v.push_back(s); }
};

bool output(Record &vehicle) {
  string plateNumber("12345");
  ofstream out(plateNumber, ios::binary);
  if (!out)
    return false;

  out.write(reinterpret_cast<char*>(&vehicle.a), sizeof(int));
  out.write(reinterpret_cast<char*>(&vehicle.b), sizeof(int));

  // Write the size of the vector.
  size_t size = vehicle.v.size();
  out.write(reinterpret_cast<char*>(&size), sizeof size);

  // Write the vector data.
  for (size_t i = 0; i < vehicle.v.size(); i++) {
    // Write the size of the string.
    size = vehicle.v[i].size();
    out.write(reinterpret_cast<char*>(&size), sizeof size);

    // Write the string data.
    const char *p = vehicle.v[i].data();
    out.write(p, vehicle.v[i].size());
  }

  return true;
}

bool input(Record &vehicle) {
  string plateNumber("12345");
  ifstream in(plateNumber, ios::binary);
  if (!in)
    return false;

  in.read(reinterpret_cast<char*>(&vehicle.a), sizeof vehicle.a);
  in.read(reinterpret_cast<char*>(&vehicle.b), sizeof vehicle.b);

  // Read the size of the vector.
  size_t vsize = 0;
  in.read(reinterpret_cast<char*>(&vsize), sizeof vsize);

  // Read the vector data.
  for (size_t i = 0; i < vsize; i++) {
    // Read in the size of the string.
    size_t size = 0;
    in.read(reinterpret_cast<char*>(&size), sizeof size);

    // Read in the string.
    char str[1000];
    in.read(str, size);
    vehicle.v.push_back(str);
  }

  return true;
}

int main() {
    Record v(1, 2);
    v.add("one");
    v.add("two");
    v.add("three");
    
    if (!output(v)) {
        cerr << "Error writing file.\n";
        return 1;
    }

    Record w;
    if (!input(w)) {
        cerr << "Error reading file.\n";
        return 1;
    }

    cout << w.a << ", " << w.b << '\n';
    for (const auto &s: w.v)
        cout << s << '\n';

    return 0;
}

Last edited on
Thank you so much @tpb! Ill work to implement your explanation into my code! I was using a vector of char out of inexperience lol. I knew that you couldn't write a string in binary to a file, so I attempted it with char from past experience. The joys of learning! ;)
That isn't true at all. You can write strings to a binary file all day long. If you want your records to all be the same length, you may need to pad or truncate the strings in the file, but that is doable. If most of the data is textual, this may not be the best approach, but there is nothing preventing you from cooking up a working solution.

A binary file does not "have" to have fixed record length, for that matter. Its handy if you want to seek the nth record, but that isn't always critical. Sometimes, you read it from front to back and parse it similar to a text file, and that is all you need. EG a dll or library isn't fixed length records but its sure not a text file :)


Last edited on
Topic archived. No new replies allowed.