views:

377

answers:

2

Consider the following C++ program, which takes a file and prints each line. It's a slice of a larger program where I later append to the file, based on what I see.

#include <fstream>
using std::fstream;
#include <iostream>
#include <string>
using std::string;

int main()
{
 fstream file("file.txt", fstream::in | fstream::out | fstream::app);

 string line;
 while (std::getline(file, line))
  std::cerr << line << std::endl;

 return 0;
}

Now apply this version of file.txt (One word on the first line, followed by a newline):

Rain

On my machine (Snow Leopard), this prints out nothing. On closer inspection, the first call to getline fails. Strangely, it also fails if I add a second line: still nothing is printed!

Can anyone solve this mystery?

+2  A: 

You should check if the file has actually been opened:

if (!file)
    std::cerr << "Oh dear" << std::endl;

Update: in fact the file likely has been opened, but is in append mode - see Neii's answer.

Update 2: okay, wrong again. In Leopard's g++ at least, the file will not be opened because the app flag is incompatible with the in flag. So the above check will print Oh dear.

In MSVC++, it goes ahead and opens the file, apparently with the read position at the start, which explains why other people saw it work and I apologise for doubting their veracity!

Daniel Earwicker
+7  A: 

When you say:

fstream file("file.txt", fstream::in | fstream::out | fstream::app);

you open the file in append mode - i.e. at the end. Just open it in read mode:

fstream file("file.txt", fstream::in );

or use an ifstream:

ifstream file("file.txt" );

And of course as Earwicker suggests, you should always test that the open succeeded.

If you are determined to open in append mode, you can move the read pointer explicitly:

#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main() {
    fstream file( "afile.txt", ios::in | ios::out | ios::app );
    if ( ! file.is_open()  ) {
        cerr << "open failed" << endl;
        return 1;
    }
    else {
        file.seekg( 0, ios::beg );   // move read pointer
        string line;
        while( getline( file, line ) ) {
            cout << line << endl;
        }
    }
}

Edit: It seems that the combination of flags used in the opening of the file leads to implementation specific behaviour. The above code works with g++ on Windows, but not with g++ on Linux.

anon
But isn't the read position independent of the write position?
Daniel Earwicker
Apparently not - this is the right answer. The folks who said they tried it on their own systems may have been fibbing! :)
Daniel Earwicker
@Earwicker - that's a strong accusation, and sadly for you a totally unwarranted one. And to think that I upvoted your answer...
Manuel
`in | out | app` is equivalent to `fopen` with `a+` which positions the read position at the start of the file so the original code should work on a conforming implementation.
Charles Bailey
I totally take it back - I should have known it would be Microsoft's fault! :)
Daniel Earwicker
@Charles - is that how the C++ standard puts it? I don't have a copy but most unofficial references I'm finding say `app` is simply incompatible with writeable streams.
Daniel Earwicker
@Earwicker - OK, no hard feelings :)
Manuel
@Earwicker: Yes, see table 92 in section 27.8.1.3 [lib.filebuf.members] and also the extra paragraphs about `ios_base::ate`. Of course you then have to look up `fopen` in the C standard to be sure what the modes mean. I don't think the C standard is abundantly clear about the initial read position for files opened in `a+` mode, though.
Charles Bailey
@Neil - your updated example prints "open failed" on Leopard. Remove the `::app` and it prints the contents of the file. So is Leopard broken here? It seems to be in the minority so far from what others are saying.
Daniel Earwicker
@Charles - ah, so in that case it is almost undefined behaviour... C++ says look at the C standard, the C standard leaves it vague.
Daniel Earwicker
I probably should say implementation-defined, not undefined.
Daniel Earwicker
@Earwicker Hmm - don't know. I compiled and ran it with g++ 4.4.1 on Windows, where it worked. As others have said, the file streams section of the standard is not its most user-friendly bit! What happens if you remove the ios::in?
anon
@Earwicker: Actually, it's implementation defined: 7.19.3 says that it's implementation defined whether the file is positioned at the start or the end of the stream for files opened in append mode so Neil's advice to explicitly seek is the only portable answer (assuming append mode is required).
Charles Bailey
So... score zero for the one-button-mouse crew. Now I have to apologise to Microsoft as well.
Daniel Earwicker
@Earwicker Out of interest, I ran my code on my ancient Linux box, and the file open failed there too. So we can't blame Apple, I'm afraid.
anon
But we can *chide* them, at least? Or look askance?
Daniel Earwicker