Michael Blake
Michael Blake

Reputation: 995

Reading a binary file into a class object

I want to be able to write a linked list of type Music into a binary file, then later, read it back into a linked list. Of course, I'm having problems doing so. From what I understand, my problem is that string is a variable length. Since a string isn't always the same size, when reading it, there's no telling how much to read. I was going to convert my class to fixed-length cstrings to fix the problem. Actually, I tried to, but had a few other problems that were caused by that. My question is, is there any way to do achieve what I want without drastically altering my code?

void ReadBinFile(Node * head, string filename)
{
    Music new_song;
    ifstream bin(filename, ios::in | ios::binary);
    if (bin.is_open())
    {
        while (!bin.eof())
        {
            bin.read(reinterpret_cast<char *>(&new_song), sizeof(Music));
            if (!bin.eof())
            {
                Node * new_node = CreateNode(new_song);
                AppendNode(head, new_node);
            }
        }
        bin.close();
    }
}
void Save(Node * head, string filename)
{
    ofstream bin(filename, ios::out | ios::binary);
    if (bin.is_open())
    {
        Node * traveler = head;
        while (traveler != nullptr)
        {
            bin.write(reinterpret_cast<char *>(&(traveler->song)), sizeof(Music));
            traveler = traveler->next;
        }
        bin.close();
    }
}
Node * CreateNode(Music song)
{
    Node * new_node = new Node;
    new_node->song = song;
    new_node->next = nullptr;

    return new_node;
}
//music.h
class Music
{
    public:
        Music(string name = "", string artist = "");

    private:
        string m_name;
        string m_artist;
};

Upvotes: 0

Views: 1464

Answers (2)

AndersK
AndersK

Reputation: 36082

Doing like this

bin.read(reinterpret_cast<char *>(&new_song), sizeof(Music));

Will not end well, if you look at your Music class

class Music
{
    public:
        Music(string name = "", string artist = "");

    private:
        string m_name;
        string m_artist;
    ...

the read will just overwrite the strings which does not take into account the string template instantiation.

To handle this overload the streaming operators >> and << so that you can in your code read from the file like this:

...
Music song;
bin >> song;
...

To do this add outside your Music class functions to pack and unpack the instance.

E.g.

std::ostream& operator<<(std::ostream& out, const Music &in)
{
  return out << in.m_name.length() << in.m_name.c_str() 
        << in.m_artist.length() << in.m_artist.c_str();
}

std::istream& operator>>(std::istream& is, Music &in)
{
  // pseudo code
  // read length, read characters of name put into "in"
  // read length, read characters of artist put into "in"
}

inside those functions you would convert to the binary format of your choice. A binary file is just data that is interpreted in a raw way and is not really different from a text file which is also is a binary file except one assumes a certain format e.g. readable characters and maybe newline characters.

if you want to store in a binary form you need to figure out a way to store enough information so that you can retrieve it later. e.g. if you store a string you can either store the length of the string first then the characters after that or store the string and have a ending \0 at the end so you know when to stop when reading the string.

Upvotes: 1

MKK
MKK

Reputation: 140

Unfortunately You use strings, which are classes on their own (with dynamically assigned length). If You want to write a whole class, it'd have to be all-constant-size, but even then I wouldn't recommend it.

Add Read and Write methods to the class and read/write each field on it's own.


As a clarification:
You want to change Music like that:
class Music { public: Music(string name = "", string artist = ""); void Write(std::string filename); void Read(str::string filename); private: string m_name; string m_artist; };
Then you can open a file in binary mode and write name, then write artist:
size_t len = str.size();
you write the length of the string
file.write(reinterpret_cast<const char*>(&len), sizeof(len));
then write the actual string data:
file.write(str.c_str(), len);


And then You read the same way -> You read the size first, then read that many chars ;)
It would restructure Your code a little bit, but I think it'd be the right way to do this.

Upvotes: 1

Related Questions