ifstream functor (C++ Primer Plus Exercise)

Hi

So this is an exercise problem from C++ Primer Plus, I can't say I really understand the question fully but have done my best to answer it, question is as follows:


Here is part of a program that reads keyboard input into a vector of string objects,
stores the string contents (not the objects) in a file, and then copies the file contents
back into a vector of string objects:

int main()
{
using namespace std;
vector<string> vostr;
string temp;

// acquire strings
cout << "Enter strings (empty line to quit):\n";
while (getline(cin,temp) && temp[0] != '\0')
vostr.push_back(temp);
cout << "Here is your input.\n";
for_each(vostr.begin(), vostr.end(), ShowStr);

// store in a file
ofstream fout("strings.dat", ios_base::out | ios_base::binary);
for_each(vostr.begin(), vostr.end(), Store(fout));
fout.close();

// recover file contents
vector<string> vistr;
ifstream fin("strings.dat", ios_base::in | ios_base::binary);
if (!fin.is_open())
{
cerr << "Could not open file for input.\n";
exit(EXIT_FAILURE);
}
GetStrs(fin, vistr);
cout << "\nHere are the strings read from the file:\n";
for_each(vistr.begin(), vistr.end(), ShowStr);

return 0;
}

Note that the file is opened in binary format and that the intention is that I/O beaccomplished with read() and write(). Quite a bit remains to be done:

1. Write a void ShowStr(const string &) function that displays a string
object followed by a newline character.

2. Write a Store functor that writes string information to a file.The Store
constructor should specify an ifstream object, and the overloaded
operator()(const string &) should indicate the string to write.A workable
plan is to first write the string’s size to the file and then write the string
contents. For example, if len holds the string size, you could use this:
os.write((char *)&len, sizeof(std::size_t)); // store length
os.write(s.data(), len); // store characters
The data() member returns a pointer to an array that holds the characters in
the string. It’s similar to the c_str() member except that the latter appends a
null character.

3. Write a GetStrs() function that recovers information from the file. It can
use read() to obtain the size of a string and then use a loop to read that
many characters from the file, appending them to an initially empty temporary
string. Because a string’s data is private, you have to use a class method to
get data into the string rather than read directly into it.



My solution is half cooked:
main.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
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 <vector>
#include <algorithm>
#include "Store.h"

void ShowStr(const std::string &);
void GetStrs(std::ifstream &, std::vector<std::string>);

int main()
{
	using namespace std;
	vector<string> vostr;
	string temp;

	// acquire strings
	cout << "Enter strings (empty line to quit):\n";
	while (getline(cin,temp) && temp[0] != '\0')
	vostr.push_back(temp);
	cout << "Here is your input.\n";
	for_each(vostr.begin(), vostr.end(), ShowStr);

	// store in a file
	ofstream fout("strings.dat", ios_base::out | ios_base::binary);
	for_each(vostr.begin(), vostr.end(), Store(fout));
	fout.close();

	// recover file contents
	vector<string> vistr;
	ifstream fin("strings.dat", ios_base::in | ios_base::binary);
	if (!fin.is_open())
	{
		cerr << "Could not open file for input.\n";
		exit(EXIT_FAILURE);
	}
	GetStrs(fin, vistr);
	cout << "\nHere are the strings read from the file:\n";
	for_each(vistr.begin(), vistr.end(), ShowStr);

	return 0;
}



void ShowStr(const std::string & str)
{
	std::cout << str << std::endl;
}



void GetStrs(std::ifstream & fin, std::vector<std::string> vec)
{

	//	Storage format:
	//	INT SIZE "\n"
	//	CHAR "\n"

	std::string temp;
	char ch;
	int chars=0;

	while(!fin.eof())
	{
		temp.clear();					// Reset string to empty.
		(fin >> chars).get();			// Get the number of chars and remove carriage return from stream.
		for(int i=0; i<chars; i++)
		{
			fin >> ch;
			temp.push_back(ch);
		}
		fin.get();						// Remove carriage return from stream.
	}
}


Store.h
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
#ifndef STORE_H_
#define STORE_H_

#include <fstream>
#include <string>

class Store
{
private:
	std::ofstream os;
public:
	Store(std::ofstream & ofs) : os(ofs){};
	~Store();
	std::ofstream & operator()(const std::string & s)
	{
		int len = sizeof(s);
		os.write((char *)&len, sizeof(std::size_t));	// store length
		os << std::endl;
		os.write(s.data(), len); 						// store characters
		os << std::endl;

		return os;
	}
};


#endif /* STORE_H_ */ 


I am getting a compile error:
C2248: 'std::basic_ios<_Elem,_Traits>::basic_ios' : cannot access private member declared in class 'std::basic_ios<_Elem,_Traits>'


Error relates to an attempt to copy an in/out stream, which is illegal in C++. Could this be due to the Store constructor? Again this is not the solution to the problem, need to see and debug its output to a file before I can formulate the proper GetStrs() function.


/Thanks
std::ofstream don't have copy constructor as I remember.

>> Store(std::ofstream & ofs) : os(ofs){};

this line is incorrect, you can't copy ofstream here.

You could try to use

1
2
private:
	std::ofstream& os;


but you can create a lot of problems here...
I have a better idea! Instead of copy-constructing the ofstream object, why dont you construct it from a c-string that represents the file path, as usual? Replace the line Store(std::ofstream & ofs) : os(ofs){}; with Store(const char* file): os(file){}; and it should work!
Last edited on
Thanks for all the help!

I think for the most part this question is messed up, it asks me to make an ifstream, but the sample code included clearly shows an ofstream object being passed (line 24). That line shows the ofstream object being passed to the constructor... seems like this question was just rushed in for the sake of having a new question in the chapter from the last edition of the book, it was never proofed or tested.

Ivan you're right, trying to copy a ofstream to a reference does open a can of worms best left untouched.

viliml you're solution is correct, and what I would have used from the get go had the sample code in the question not led me to look for another solution. It really is the only way to go about this.

Anyone know if there is a member function in the ofstream that can be invoked to get the name of output file its using? Perhaps I could pass the ofstream object to the functor, use the ofstream member function to get the file name and open it like like vilimil suggested. I've read through the oftream reference on the site, nothing listed there that would allow you to pull the name of the file.


/Thanks
So updating the Store class to the one below

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
#ifndef STORE_H_
#define STORE_H_

#include <fstream>
#include <string>

class Store
{
private:
	std::ofstream & os;
public:
	Store(const char * filename) : os(filename, std::ios_base::out | std::ios_base::binary)
	{}
	~Store(){os.close();};
	std::ofstream & operator()(const std::string & s)
	{
		int len = sizeof(s);
		os.write((char *)&len, sizeof(std::size_t));	// store length
		os << std::endl;
		os.write(s.data(), len); 						// store characters
		os << std::endl;

		return os;
	}
};


#endif /* STORE_H_ */ 


produces the following errors on line 12/13
C2359: 'Store::os' : member of non-class type requires single initializer expression
C2439: 'Store::os' : member could not be initialized
C2440: 'initializing' : cannot convert from 'int' to 'std::ofstream &'


Any ideas?


/Thanks
I don't know why does it make such errors, but there is an error that compiles but is very wrong that I can see!
1
2
int len = sizeof(s);
		os.write((char *)&len, sizeof(std::size_t));
=WTH???
If you want to store the size, use itoa()!
closed account (DSLq5Di1)
You were not far off the mark with your first post,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Store
{
private:
	std::ofstream& os;
public:
	Store(std::ofstream & ofs) : os(ofs){};
	~Store();
	std::ofstream & operator()(const std::string & s)
	{
		int len = sizeof(s);
		size_t len = s.length();
		os.write((char *)&len, sizeof(std::size_t));	// store length
		os << std::endl;
		os.write(s.data(), len); 						// store characters
		os << std::endl;

		return os;
	}
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void GetStrs(std::ifstream & fin, std::vector<std::string>& vec)
{
	int chars=0;
	size_t len = 0;

	while(!fin.eof())
	while (fin.read((char *)&len, sizeof(std::size_t)))
	{

		temp.clear();					// Reset string to empty.
		std::string temp;

		(fin >> chars).get();			// Get the number of chars and remove carriage return from stream.
		for(size_t i=0; i<len; i++)
		{
			char ch;
			fin >> ch;
			temp.push_back(ch);
		}
		fin.get();						// Remove carriage return from stream.
		vistr.push_back(temp);
	}
}
Thanks sloppy9!

I see where I was going wrong now, just one correction though - GetStrs:
vistr.push_back(temp);

should be:
vec.push_back(temp);


Thanks everyone!
GetStrs line 17:
fin >> ch;

should be:
fin.get(ch)

otherwise blank spaces are not caught and the while loop will not end.
Last edited on
Topic archived. No new replies allowed.