File reading problem

E

Eric Lilja

Hi! I have a program with a class that needs to be able to write
itself to a file in clear text format. The file has two integers and
vector of struct objects. The struct has a string that can consist of
one or more words and a few integers. I'm able to create the file
properly, as confimed by viewing it in a text editor, but something
goes wrong when I tried to read it. I've made a test program
illustrating the problem:

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct s_t
{
s_t() {}
s_t(const string& si, int xi, int yi) : s(si), x(xi), y(yi) {}
string s;
int x, y;

friend ostream& operator<<(ostream& os, const s_t& rhs);
friend istream& operator>>(istream& is, s_t& rhs);
};

ostream&
operator<<(ostream& os, const s_t& rhs)
{
os << rhs.s << endl;
os << rhs.x << ' ' << rhs.y;

return os;
}

istream&
operator>>(istream& is, s_t& rhs)
{
getline(is, rhs.s);

is >> rhs.x >> rhs.y;

return is;
}

void
readit()
{
int x = 0, y = 0;
vector<s_t> vec;

ifstream in("foo.txt");

in >> x >> y;

{
s_t temp;

while (in >> temp) vec.push_back(temp);
}

in.close();

cout << x << ' ' << y << endl;
cout << vec.size() << endl;
for (unsigned int i = 0; i < vec.size(); i++)
cout << vec.at(i) << endl;
}

int
main()
{
int x = 4711, y = 1337;
vector<s_t> vec;

vec.push_back(s_t("foo bar", 33, 22));
vec.push_back(s_t("bar baz", 11, 99));

ofstream out("foo.txt");

out << x << ' ' << y << endl;
for (unsigned int i = 0; i < vec.size(); i++)
{
out << vec.at(i);

if (i < vec.size() - 1)
out << endl;
}

out.flush();
out.close();

readit();
}

When I run it, I get this output:
4711 1337
0

so it reads the first two numbers ok but reading the s_t objects
doesn't work because the vector is empty afterwards...

Any help appreciated.

- E
 
R

Robert Bauck Hamar

Eric said:
Hi! I have a program with a class that needs to be able to write
itself to a file in clear text format. The file has two integers and
vector of struct objects. The struct has a string that can consist of
one or more words and a few integers. I'm able to create the file
properly, as confimed by viewing it in a text editor, but something
goes wrong when I tried to read it. I've made a test program
illustrating the problem:

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct s_t
{
s_t() {}
s_t(const string& si, int xi, int yi) : s(si), x(xi), y(yi) {}
string s;
int x, y;

friend ostream& operator<<(ostream& os, const s_t& rhs);
friend istream& operator>>(istream& is, s_t& rhs);
};

ostream&
operator<<(ostream& os, const s_t& rhs)
{
os << rhs.s << endl;
os << rhs.x << ' ' << rhs.y;

return os;
}

istream&
operator>>(istream& is, s_t& rhs)
{
getline(is, rhs.s);

is >> rhs.x >> rhs.y;

return is;
}

void
readit()
{
int x = 0, y = 0;
vector<s_t> vec;

ifstream in("foo.txt");

in >> x >> y;

After this, the next character in the input stream should be a '\n'.
{
s_t temp;

This will now read to the first newline in the stream and put the result
into temp.s. Since the first character is a newline, temp.s will be empty.
Then in will try to put the contents of what's stored as a string into
temp.x. In this case, in finds 'f', and fails. Thus the reading of the two
ints will become a noop, and the test fails.

I suggest you look up the ignore() member function of istream. You need to
ignore the rest of the line.
while (in >> temp) vec.push_back(temp);
}

in.close();

cout << x << ' ' << y << endl;
cout << vec.size() << endl;
for (unsigned int i = 0; i < vec.size(); i++)
cout << vec.at(i) << endl;
}

int
main()
{
int x = 4711, y = 1337;
vector<s_t> vec;

vec.push_back(s_t("foo bar", 33, 22));
vec.push_back(s_t("bar baz", 11, 99));

ofstream out("foo.txt");

out << x << ' ' << y << endl;
for (unsigned int i = 0; i < vec.size(); i++)
{
out << vec.at(i);

if (i < vec.size() - 1)
out << endl;
}

out.flush();
out.close();

readit();
}

When I run it, I get this output:
4711 1337
0

so it reads the first two numbers ok but reading the s_t objects
doesn't work because the vector is empty afterwards...

Any help appreciated.

It's a common beginner error. Try to hand trace the code. Write down the
contents of the file on paper with control characters, and see where the
next char to be read is.
 
E

Eric Lilja

After this, the next character in the input stream should be a '\n'.




This will now read to the first newline in the stream and put the result
into temp.s. Since the first character is a newline, temp.s will be empty.
Then in will try to put the contents of what's stored as a string into
temp.x. In this case, in finds 'f', and fails. Thus the reading of the two
ints will become a noop, and the test fails.

I suggest you look up the ignore() member function of istream. You need to
ignore the rest of the line.

I solved it by adding this to my overloaded operator>>:
if (is.peek() == '\n')
{
is.ignore();
}
before the getline() call. Now that program works just fine. However,
the *real* program tries to read even though there are no more entries
in the file. Hmm.
 
E

Eric Lilja

I solved it by adding this to my overloaded operator>>:
if (is.peek() == '\n')
{
is.ignore();}

before the getline() call. Now that program works just fine. However,
the *real* program tries to read even though there are no more entries
in the file. Hmm.

And I solved that by checking the return value of the getline() call
and returning
the stream directly if it fails.
It's a common beginner error. Try to hand trace the code. Write down the
contents of the file on paper with control characters, and see where the
next char to be read is.
 
B

BobR

Eric Lilja said:
And I solved that by checking the return value of the getline() call
and returning the stream directly if it fails.

Your 'vector' in 'main()' is separate from the 'vector' in 'readit()'. If
you want to fill a 'vector' from 'main()', try adding this (no need to
remove your current version):

void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;
if( in.peek() == '\n' ){ in.ignore(); }
s_t temp;
while( in >> temp ) vec.push_back( temp );
// untested. replace the above two lines with:
// for( s_t temp; in >> temp; /*m_t*/ ){
// vec.push_back( temp );
// } // for(in)
} // readit( std::vector<s_t>&)

{ // main
// .....
std::vector<s_t> vst;
readit( vst );
for( std::size_t i(0); i < vst.size(); ++i )
cout << vst.at(i) << endl;
}
 
E

Eric Lilja

Your 'vector' in 'main()' is separate from the 'vector' in 'readit()'. If
you want to fill a 'vector' from 'main()', try adding this (no need to
remove your current version):

void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;
if( in.peek() == '\n' ){ in.ignore(); }
s_t temp;
while( in >> temp ) vec.push_back( temp );
// untested. replace the above two lines with:
// for( s_t temp; in >> temp; /*m_t*/ ){
// vec.push_back( temp );
// } // for(in)
} // readit( std::vector<s_t>&)

{ // main
// .....
std::vector<s_t> vst;
readit( vst );
for( std::size_t i(0); i < vst.size(); ++i )
cout << vst.at(i) << endl;

}


Umm, yeah, I know, this was just a program for illustrating a file
reading problem. I don't care in this program if I use different
vectors, it's completely irrelevant. And I know perfectly well what
passing a variable by reference means...
 
J

James Kanze

[...]
void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;

You probably want to check the return values here. But I'm not
too sure what this function is designed to do, to begin with.
if( in.peek() == '\n' ){ in.ignore(); }

What's the purpose of this line? The following "in >> temp"
will automatically skip any leading white space (and '\n' is
white space).

If the input is line oriented, and you want to verify its
format, then you should probably read using getline, then use
istringstream to parse it (not forgetting a final
if ( line >> std::ws && line.get() == EOF ) ...
to ensure that there's no trailing garbage). If it's just a
collection of white space separated s_t, then you can just read,
you don't need to "ignore" anything.
s_t temp;
while( in >> temp ) vec.push_back( temp );
// untested. replace the above two lines with:
// for( s_t temp; in >> temp; /*m_t*/ ){

Doesn't work. The first pass through the loop will use an
uninitialized temp.
 
E

Eric Lilja

[...]
void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;

You probably want to check the return values here. But I'm not
too sure what this function is designed to do, to begin with.
if( in.peek() == '\n' ){ in.ignore(); }

What's the purpose of this line? The following "in >> temp"
will automatically skip any leading white space (and '\n' is
white space).

If the input is line oriented, and you want to verify its
format, then you should probably read using getline, then use
istringstream to parse it (not forgetting a final
if ( line >> std::ws && line.get() == EOF ) ...
to ensure that there's no trailing garbage). If it's just a
collection of white space separated s_t, then you can just read,
you don't need to "ignore" anything.
s_t temp;
while( in >> temp ) vec.push_back( temp );
// untested. replace the above two lines with:
// for( s_t temp; in >> temp; /*m_t*/ ){

Doesn't work. The first pass through the loop will use an
uninitialized temp.

Huh?

and btw, the program works just fine now so I don't see the reason for
this post anyway...
 
R

Robert Bauck Hamar

James said:
[...]
void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;

You probably want to check the return values here. But I'm not
too sure what this function is designed to do, to begin with.
if( in.peek() == '\n' ){ in.ignore(); }

What's the purpose of this line? The following "in >> temp"
will automatically skip any leading white space (and '\n' is
white space).

No, it will not. in >> temp calls the user defined operator>>(istream& i,
s_t& st), which in turn commence by calling getline(i, st). Without the
in.ignore(), or using some other means of skipping the newline, this will
not work correctly.
 
E

Eric Lilja

James said:
[...]
void readit( std::vector<s_t> &vec ){ // non-const
ifstream in("foo.txt");
if( not in.is_open() ){ std::cerr<<"ERROR"; return; }
int x( 0 ), y( 0 );
in >> x >> y;
You probably want to check the return values here. But I'm not
too sure what this function is designed to do, to begin with.
What's the purpose of this line? The following "in >> temp"
will automatically skip any leading white space (and '\n' is
white space).

No, it will not. in >> temp calls the user defined operator>>(istream& i,
s_t& st), which in turn commence by calling getline(i, st). Without the
in.ignore(), or using some other means of skipping the newline, this will
not work correctly.

But the code has an ignore (did you read the entire thread?). The
reading works (tested with two different compilers) now and I'm
working on other bugs and features now.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top