Reputation: 995
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
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
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