ifstream functor (C++ Primer Plus Exercise)

Jul 16, 2012 at 1:52am
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
Jul 16, 2012 at 8:07am
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...
Jul 16, 2012 at 9:01am
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 Jul 16, 2012 at 9:04am
Jul 17, 2012 at 12:43am
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
Jul 17, 2012 at 5:15am
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
Jul 17, 2012 at 8:24am
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()!
Jul 17, 2012 at 5:05pm
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);
	}
}
Jul 17, 2012 at 11:25pm
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!
Jul 17, 2012 at 11:41pm
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 Jul 18, 2012 at 1:31am
Topic archived. No new replies allowed.