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?

Andres Jaan Tack
  • 22,566
  • 11
  • 59
  • 78

2 Answers2

9

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.

  • But isn't the read position independent of the write position? – Daniel Earwicker Feb 21 '10 at 10:03
  • Apparently not - this is the right answer. The folks who said they tried it on their own systems may have been fibbing! :) – Daniel Earwicker Feb 21 '10 at 10:05
  • @Earwicker - that's a strong accusation, and sadly for you a totally unwarranted one. And to think that I upvoted your answer... – Manuel Feb 21 '10 at 10:13
  • 1
    `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. – CB Bailey Feb 21 '10 at 10:19
  • I totally take it back - I should have known it would be Microsoft's fault! :) – Daniel Earwicker Feb 21 '10 at 10:20
  • @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 Feb 21 '10 at 10:21
  • @Earwicker - OK, no hard feelings :) – Manuel Feb 21 '10 at 10:23
  • @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. – CB Bailey Feb 21 '10 at 10:29
  • @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 Feb 21 '10 at 10:31
  • @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 Feb 21 '10 at 10:33
  • I probably should say implementation-defined, not undefined. – Daniel Earwicker Feb 21 '10 at 10:35
  • @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? –  Feb 21 '10 at 10:35
  • 1
    @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). – CB Bailey Feb 21 '10 at 10:35
  • So... score zero for the one-button-mouse crew. Now I have to apologise to Microsoft as well. – Daniel Earwicker Feb 21 '10 at 10:42
  • @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. –  Feb 21 '10 at 10:48
  • But we can *chide* them, at least? Or look askance? – Daniel Earwicker Feb 21 '10 at 11:17
2

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
  • 114,894
  • 38
  • 205
  • 284