Well, one doesn't necessarily need to marry the file input and output with the data structure you use in the program. Of course, not doing so introduces a level of indirection, but it's a very thin one that's pretty easy to deal with.
Consider a utility class called FileEntry which is just responsible for reading and writing some information to a file.
FileEntry.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#ifndef FILEENTRY_H___
#define FILEENTRY_H___
#include <string>
struct FileEntry
{
std::string name ;
unsigned sold ;
unsigned nEntries ;
};
std::ifstream& operator>>(std::ifstream& is, FileEntry& entry) ;
std::ofstream& operator<<(std::ofstream& os, const FileEntry& entry) ;
#endif
|
FileEntry.cpp:
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
|
#include <fstream>
#include <string>
#include <sstream>
#include "FileEntry.h"
const char field_delimiter = '\t' ;
std::ifstream& operator>>(std::ifstream& is, FileEntry& entry)
{
std::string input ;
if ( std::getline(is, input) )
{
std::istringstream in(input) ;
// we should probably check to make sure the following operations are
// successful, but for brevity's sake...
std::getline(in, entry.name, field_delimiter) ;
in >> entry.sold >> entry.nEntries ;
}
return is ;
}
std::ofstream& operator<<(std::ofstream& os, const FileEntry& entry )
{
// output is easy!
os << entry.name << '\t' << entry.sold << '\t' << entry.nEntries << '\n' ;
return os ;
}
|
A couple things bear explaining. I don't store the "Average per entry" because it can be easily calculated from other data we're storing. I don't know whether name should consist of a string with a space in it (for example a first and last name,) so to get around that I assume that the file is tab delimited (ie. there's a '\t' character between each data item on a given line.) operator<< and operator >> are just responsible for writing and reading one line of data to a file. Once that's known, I think the code is easy enough to understand.
You'll notice that there is no notion of containers in this code. So, how do we use it?
Well, let's a assume we have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
class Whatever
{
public:
// ...
bool writeToFile( const std::string& fname ) const ;
bool readFromFile( const std::string& fname ) ;
// ...
private:
struct OrdersInfo
{
unsigned sold, entries ;
OrdersInfo(unsigned s, unsigned e) : sold(s), entries(e) {}
};
std::vector<std::string> names ;
std::vector<OrdersInfo> info ;
};
|
Whatever is responsible for managing a parallel set of related vectors with a one-to-one relationship. It is obviously an incomplete type, but it should serve for illustration purposes. You'll notice there is no mention of a FileEntry in the class definition. The only place any mention of a FileEntry need show up is in the definition of the member functions that read and write from a file, in this case Whatever::readFromFile and Whatever::writeToFile. In that sense a FileEntry is just an implementation detail of Whatever.
Let's take a look at possible implementations for read and write:
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
|
bool Whatever::writeToFile(const std::string & fname) const
{
std::ofstream out(fname, std::ios::out | std::ios::trunc) ;
for ( unsigned i=0; i<names.size(); ++i )
{
// create a FileEntry object with our data and write it to the file.
const FileEntry entry = { names[i], info[i].sold, info[i].entries } ;
out << entry ;
}
return out ;
}
bool Whatever::readFromFile(const std::string & fname)
{
std::ifstream in(fname) ;
if ( in.is_open() )
{
// If we're reading from file, we want to get rid of
// the info that's already in memory.
names.clear() ;
info.clear() ;
FileEntry entry ;
// read a FileEntry object from the file and convert it to our format.
while ( in >> entry )
{
names.push_back(entry.name) ;
info.push_back(OrdersInfo(entry.sold, entry.nEntries)) ;
}
return true ;
}
return false ;
}
|
I think the code and comments are fairly straightforward, but if you've got any questions feel free to ask. None of this code has been tested, by the way, so: user beware!
Depending on what you're doing, std::map may be a better choice for the internals of "Whatever"