I've got a custom class that is as follows:

class course {
   private:
      string prefix;
      int number;
      string title;
      int credits;
      char grade;
   public:
      //Constructors
      course();
      course(string, int, string, int, char);
      //Accessors
      void set();
      void print() const;
      string get_prefix() const;
      int get_number() const;
      string get_title() const;
      int get_credits() const;
      char get_grade() const; 
      //Operators
      bool operator < (course) const;
      bool operator > (course) const;
      course operator = (course);
      bool operator == (course) const;
      bool operator == (int n) const;
      bool operator != (course) const;
};

//.... then in main

vector<course> cache; //holds courses in memory
vector<course>::iterator point; //points at the cache
course temp; //buffer
fstream file;//for file I/O

And I'm trying to do file input and output as follows:

cout << "Loading file..." << endl;
file.open("Courses.dat", ios::in | ios::binary);
file.clear();
file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
while (!file.eof()) { 
   cache.push_back(temp);
   temp.print();
   file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
}
file.close();
cout << "Saving to file..." << endl;
file.open("Courses.dat", ios::out | ios::trunc | ios::binary);
file.clear();
for(point = cache.begin(); point != cache.end(); point++) {
   temp = *point;
   temp.print();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   file.clear();
}     
file.close();

What's interesting is if I write and read pre-made data like below, it works:

course one("CSCI", 2012, "C++ Programming 2", 3, 'A');
course two("MATH", 2081, "Multiple Varible Calculus", 4, 'C');
course three("ENGL", 1022, "Composition 2", 3, 'B');
file.open("Courses.dat", ios::out | ios::trunc | ios::binary);
file.write(reinterpret_cast<char *>(&one), sizeof(one));
file.write(reinterpret_cast<char *>(&two), sizeof(two));
file.write(reinterpret_cast<char *>(&three), sizeof(three));
file.close();

//close program, open it again without writing that data
//load data into cache, and it displays correctly

.But if I use any custom data (AKA user-input) it saves correctly as far as I can tell, but if I try to load the data it crashes when it tries to display it - printing out random gibberish.


Can anyone spot any errors I missed in the file I/O routines? If need be I can post the entire source code.
Thanks in advance for wading through this post!

Your class doesn't meet the requirements for a POD type, so using read and write is undefined. Add a couple of member functions that serialize your class to and from a string, then use that string for file I/O.

Um... by that do you mean overload the << and >> operators?

I'm basing my work off my textbook ("Object-Oriented Programming in C++" by Robert Lafore"), who uses the following example:

// diskfun.cpp
// reads and writes several objects to disk
#include <fstream>                //for file streams
#include <iostream>
using namespace std;
////////////////////////////////////////////////////////////////
class person                      //class of persons
   {
   protected:
      char name[80];              //person's name
      int age;                    //person's age
   public:
      void getData()              //get person's data
         {
         cout << "\n   Enter name: "; cin >> name;
         cout << "   Enter age: "; cin >> age;
         }
      void showData()             //display person's data
         {
         cout << "\n   Name: " << name;
         cout << "\n   Age: " << age;
         }
   };
////////////////////////////////////////////////////////////////
int main()
   {
   char ch;
   person pers;                   //create person object
   fstream file;                  //create input/output file
                                  //open for append
   file.open("GROUP.DAT", ios::app | ios::out |
                                      ios::in | ios::binary );
   do                             //data from user to file
      {
      cout << "\nEnter person's data:";
      pers.getData();             //get one person's data
                                  //write to file
      file.write( reinterpret_cast<char*>(&pers), sizeof(pers) );
      cout << "Enter another person (y/n)? ";
      cin >> ch;
      }
   while(ch=='y');                //quit on 'n'
   file.seekg(0);                 //reset to start of file
                                  //read first person
   file.read( reinterpret_cast<char*>(&pers), sizeof(pers) );
   while( !file.eof() )           //quit on EOF
      {
      cout << "\nPerson:";        //display person
      pers.showData();            //read another person
      file.read( reinterpret_cast<char*>(&pers), sizeof(pers) );  
      }
   cout << endl;
   return 0;
   }

Do you mean because the strings are of undefined length, the file I/O messed up?

NOTE: Interestingly, when I tried again, here is the output:

CSCI 2012: C++ Programming 2
   Worth 3 credits. Grade: A
MATH 2081: Multiple Varible Calculus
   Worth 4 credits. Grade: C
ENGL 1022: Composition 2
   Worth 3 credits. Grade: B
 2022:
   Worth 4 credits. Grade: C

Notice the last entry: it read the integers and char correctly, just missed the strings. Not crashing anymore...

>Um... by that do you mean overload the << and >> operators?
If you want. I think explicit serialize and deserialize member functions are more flexible though.

>I'm basing my work off my textbook ("Object-Oriented Programming in C++" by Robert Lafore")
You have my sympathy then. Lafore doesn't come to mind when I think of good writers, and he's on the list when I think of bad writers.

>Do you mean because the strings are of undefined length, the file I/O messed up?
I mean that your objects aren't guaranteed to be convertable to an array of unsigned char due to padding and other internal data structure. That's an absolute requirement when using read or write because you're casting your object to an array of char (roughly the same thing). Do a google search for "plain old data". Those are the rules you have to abide by if you want to be able to cast your objects to an unsigned char* (and you should be using unsigned char* rather than char*).

>Not crashing anymore...
Undefined behavior means anything could happen. It could work for a year and then crash every day for a month straight or vice versa.

Gaaaah, okay, now its crashing again...

Yea, I'm also using a textbook by Tony Gaddis, it's just not with me.

Hmm... so if I created a struct to hold my data, built a class around that struct, then wrote only the STRUCT to disk it would work better? I'll start on that, yell at me if I missed the boat again.


Sorry for the confuzzlement, I've never heard of any of this POD stuff, our professor just throws up powerpoints and hopes we get it ><

Wow. I have to re-write everything to deal with structs?
This sucks.

Ooooookay. So I re-wrote everything to write a struct to file, instead of a class. I made a struct called cinfo that I create from a class to write a file, and made a constructor to create a class from one of those structs (for reading data back in).

But the same exact error happens! I must be missing something different.

// This uses classes and STL to manage a listing of courses.
// Created by:
// Created: 5/3/07
// Modified: 5/4/07
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <functional> // (?)
using namespace std;

struct cinfo { //holds course info
   string prefix;
   int number;
   string title;
   int credits;
   char grade;
};

class course {
   private:
       string prefix;
       int number;
       string title;
       int credits;
       char grade;
   public:
      //Constructors
      course();
      course(string, int, string, int, char);
      course(cinfo in);
      //Accessors
      void set();
      void print() const;
      string get_prefix() const;
      int get_number() const;
      string get_title() const;
      int get_credits() const;
      char get_grade() const;
      cinfo get_info() const;
      //Operators
      bool operator < (course) const;
      bool operator > (course) const;
      course operator = (course);
      bool operator == (course) const;
      bool operator == (int n) const;
      bool operator != (course) const; 
}; 

//class functions...
//indenting below is a little screwed up, my bad

int main()
{
   vector<course> cache; //holds courses in memory
   vector<course>::iterator point; //points at the cache
   cinfo temp        //buffer struct
   course *buffer; //buffer class
   fstream file;//for file I/O
 
  //stuff happens

 file.open("Courses.dat", ios::out | ios::trunc | ios::binary);
   temp = one.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   temp = two.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   temp = three.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   file.close();



cout << "Saving to file..." << endl;
file.open("Courses.dat", ios::out | ios::trunc | ios::binary);
file.clear();
for(point = cache.begin(); point != cache.end(); point++) {
   temp = point->get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   file.clear();
}     
file.close();//close file



 cout << "Loading file..." << endl;
 file.open("Courses.dat", ios::in | ios::binary);
 file.clear();
 file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
 while (!file.eof()) {  //read in file
     buffer = new course(temp);
     cache.push_back(*buffer);//place object in cache
     delete buffer;
     file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
}
file.close();

Attached is the entire source code. I cannot upload the blank "Courses.dat" file, so create a new ".txt" file in the same folder and rename it "Courses.dat" for it to work. The prompt at the beginning allows you to add three structs to file, so you don't have to type data every time you blank the .dat file.

Attachments
// This uses classes and STL to manage a listing of courses.
// Created by: 
// Created:  5/3/07
// Modified: 5/4/07
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <functional> // (?)
using namespace std;

struct cinfo { //holds course info
   string prefix;
   int number;
   string title;
   int credits;
   char grade;
};

class course {
   private:
       string prefix;
       int number;
       string title;
       int credits;
       char grade;
   public:
      //Constructors
      course();
      course(string, int, string, int, char);
      course(cinfo in);
      //Accessors
      void set();
      void print() const;
      string get_prefix() const;
      int get_number() const;
      string get_title() const;
      int get_credits() const;
      char get_grade() const;
      cinfo get_info() const;
      //Operators
      bool operator < (course) const;
      bool operator > (course) const;
      course operator = (course);
      bool operator == (course) const;
      bool operator == (int n) const;
      bool operator != (course) const; 
}; 
course::course() //default constructor
{
   prefix = "";
   number = 0;
   title = "";
   credits = 0;
   grade = 'F';
}
course::course(string pre, int n, string ti, int cred, char g)//constructor
{
   prefix = pre;
   number = n;
   title = ti;
   credits = cred;
   grade = g;
   if (grade < 'A' || grade > 'F' || grade == 'E')
      { grade = 'F'; } //if invalid grade, give an F
}
course::course(cinfo in)//constructor from struct
{
   prefix = in.prefix;
   number = in.number;
   title = in.title;
   credits = in.credits;
   grade = in.grade;
}
void course::set() //get data from user
{
   cout << " Input prefix:    ";
   cin >> prefix;
   cout << " Input course #:  ";
   cin >> number;
   cout << " Input title:     ";
   cin.ignore();
   getline(cin, title);
   cout << " Input # credits: ";
   cin >> credits;
   grade = 'E'; //spoof data for input validation
   while (grade < 'A' || grade > 'F' || grade == 'E') {
         cout << " Input grade:     ";
         cin >> grade;
   }
}
void course::print() const //print out stored data
{
   cout << prefix << " " << number << ": " << title << endl
        << "   Worth " << credits << " credits. Grade: " << grade << endl;
}
string course::get_prefix() const //returns class prefix
{ return prefix; }
int course::get_number() const   //returns class number
{ return number; }
string course::get_title() const //returncs class title
{ return title; }
int course::get_credits() const  //returns class credits
{ return credits; }
char course::get_grade() const   //returns class grade
{ return grade; }
cinfo course::get_info() const  //returns class data struct
{
   cinfo bob;
   bob.prefix = prefix;
   bob.number = number;
   bob.title = title;
   bob.credits = credits;
   bob.grade = grade;
   return bob;
}
bool course::operator < (course n) const //sorts by class #
{
   if (number < n.number) {
      return true;
   } else {
      return false;
   }
}
bool course::operator > (course n) const //sorts by class #
{
   if (number > n.number) {
      return true;
   } else {
      return false;
   }
}
course course::operator = (course n) //assignment opperator
{
   prefix = n.prefix;
   number = n.number;
   title = n.title;
   credits = n.credits;
   grade = n.grade;
   return *this;
}
bool course::operator == (course n) const //returns based on class #
{
   if (number == n.number) {
      return true;
   } else {
      return false;
   }  
}
bool course::operator == (int n) const //returns based on class #
{//overloaded for use with an integer
   if (number == n) {
      return true;
   } else {
      return false;
   }
}
bool course::operator != (course n) const //returns based on class #
{
   if (number != n.number) {
      return true;
   } else {
      return false;
   }  //cond1?a:b ***********************************************************************
}        
//----------------------------------------------------------------------
//+++++++++++++END COURSE CLASS FUNCTION DEFINITIONS++++++++++++++++++++
//----------------------------------------------------------------------

class display //class object to display courses
{//target can be used to display only courses that have
 //the specified number, or target can be set to -1
 //to display any course.
   public:
      int target; //which element(s) to display
      explicit display(int n) //accepts target modifier
        { target = n; }
      void operator() (const course ptr) const
      {  if(target == -1 || ptr.get_number() == target)
           { ptr.print(); }
      }
};

int main()
{
   vector<course> cache; //holds courses in memory
   vector<course>::iterator point; //points at the cache
   int choice = 0; //user's menu choice
   char select;    //user's menu choice (for y/n questions)
   double sum, creds; //for GPA
   int target; //for searching
   cinfo temp;     //buffer struct for output
   course *buffer; //buffer class for input
   cout << fixed << showpoint << setprecision(2);  //for GPA format
   fstream file;//for file I/O
   
   //test data
   course one("CSCI", 2012, "C++ Programming 2", 3, 'A');
   course two("MATH", 2081, "Multiple Varible Calculus", 4, 'C');
   course three("ENGL", 1022, "Composition 2", 3, 'B');
   cout << "Write test data? [Y/N] ";
   cin >> select;
   if (select == 'y' || select == 'Y') { 
   cout << "Writing data...." << endl;
   file.open("Courses.dat", ios::out | ios::trunc | ios::binary);
   temp = one.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   temp = two.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   temp = three.get_info();
   file.write(reinterpret_cast<char *>(&temp), sizeof(temp));
   file.close();
   }
   
   while (choice != 8)
   {
      cout << "---COURSE MANAGEMENT---" << endl
           << " [1] Load file" << endl
           << " [2] Add course" << endl
           << " [3] Search for a course" << endl
           << " [4] Delete a course" << endl
           << " [5] Display courses" << endl
           << " [6] Calculate GPA" << endl
           << " [7] Save to file" << endl
           << " [8] Exit program" << endl << endl
           << "Input option: ";
      cin >> choice; //display menu & get user's input
      
      switch(choice) {
         case 1: //open a file, load it to cache
              cout << "Loading file..." << endl;
              file.open("Courses.dat", ios::in | ios::binary);
              file.clear();
              file.read(reinterpret_cast<char *>(&temp), sizeof(temp));//read first object
              while (!file.eof()) {  //read in file
                 buffer = new course(temp);
                 cache.push_back(*buffer);//place object in cache
                 delete buffer;
                 file.read(reinterpret_cast<char *>(&temp), sizeof(temp));//read next object
              }
              file.close();
              cout << "File loaded." << endl << endl;
              break;
         case 2: //add a course to the cache
              buffer = new course;
              buffer->set();           //get data
              cache.push_back(*buffer);//store in cache
              delete buffer;
              break;
         case 3: //search cache for a course
              cout << "Input course number to search for: ";
              cin >> target;
              cout << "Any found courses displayed below:" << endl;
              for_each(cache.begin(), cache.end(), display(target) );
              cout << endl << endl;
              break;
         case 4: //delete a course from cache
              cout << "Input course number to delete: ";
              cin >> target;
              point = cache.begin();
              cache.erase ( remove ( cache.begin(), cache.end(), target ), cache.end() );
              cout << "Any courses with the number " << target << " have been deleted." << endl << endl;
              break;
         case 5: //display all courses in cache
              target = -1; //tells display class to display all courses
              for_each(cache.begin(), cache.end(), display(target) );
              cout << endl;
              break;
         case 6: //calculate GPA
              sum = 0, creds = 0; //they are doubles to division is easyier
              point = cache.begin();
              while(point != cache.end()) {
                 switch(point->get_grade()) {//accumulate credit score
                    case 'A':
                         sum += (4 * point->get_credits());
                         break;
                    case 'B':
                         sum += (3 * point->get_credits());
                         break;
                    case 'C':
                         sum += (2 * point->get_credits());
                         break;
                    case 'D':
                         sum += (1 * point->get_credits());
                         break;
                    case 'F':
                         break;
                    default://bad data
                         cout << "ERROR: Invalid grade in course " << (point->get_number()) << endl
                              << "Will ignore and continue calculating." << endl;
                         break; }
                 creds += point->get_credits(); //accumulate credits
                 point++; //increment iterator
              }
              cout << "GPA: " << sum / creds << endl << endl;
              break;
         case 7: //save cache to file
              cout << "This operation will delete any information in the selected file."
                   << endl << "Continue? [Y/N] ";
              cin >> select;
              if(select == 'y' || select == 'Y') {//if user chosses to go on, write to file
                 cout << "Saving to file..." << endl;

>Wow. I have to re-write everything to deal with structs?
No. You're so focused on trying to write an object to file, you're completely missing the point. All you need to do is turn the object into something that can be written to file. For example:

// Convert this to a pipe delimited string
const string course::serialize()
{
  stringstream out;

  out<< prefix <<'|'<< number <<'|'<< title <<'|'<< credits <<'|'<< grade;

  return out.str();
}

Then you can do this:

file<< pers.serialize();

Oh, God.... we even did something like this a long time ago, I just didn't think we still wanted to use that method...

I'll whip up an in/out and let you know how it goes ;)

This article has been dead for over six months. Start a new discussion instead.