When would you ever need to re-use a file stream?

Pages: 12
So, I see a lot of code (even the tutorial on this site) that explicitly use the .open() and .close() member functions of file streams. In my opinion, these member functions are pretty useless - the constructor lets you open a file and the destructor closes it (RAII). With RAII in mind, these functions are outright bad practice.

The only time these member functions seem to be useful is if you want to re-use the file stream, but this sounds both messy and confusing. When would you ever want to do that? I can't think of a time where RAII doesn't do what you want (especially with move-ctors in C++11 and being able to store file streams in containers).

The tutorial on this site suggests this structure:
1
2
3
4
5
6
7
8
9
10
std::ifstream myfile ("example.txt");
if(myfile.is_open())
{
    //read it
    myfile.close();
}
else
{
    //display error message
}
- myfile is left in the enclosing scope, polluting it
- You have to remember to explicitly close the file at the end of the if block, or the file will stay open longer than expected
- Because of the enclosing scope pollution, someone may be tempted to 're-use' the file, but if they forgot to close it, it won't do what they want

Meanwhile, I always use and suggest this form:
1
2
3
4
5
6
7
8
if(std::ifstream myfile ("example.txt"))
{
    //read it
}
else
{
    // display error message
}
- myfile's scope is confined to the if block and does not pollute the enclosing scope
- Because of RAII the stream is automatically closed at the end of the if block
- It isn't possible to be tempted to re-use the stream

So why is there so much of the first form? When would you ever want to re-use a file stream (or any other stream, for that matter)?
I don't think such functions exist to make a file stream reusable, though by necessity such functions would have to (else fail to follow the principle of least surprise).

Rather, they allow you to manage a filestream's status with a little more control than simple construction/destruction would allow. Sometimes this matters.


As for reusing an object, I don't tend to like that idea except for "scratch variables", with names like x and types like int.
In the type of work that I do, it's very important to be able to produce meaningful diagnostic error messages when something fails. If the file couldn't be opened or closed, then how would you determine the reason if doing it in the constructor/destructor? For this reason, we usually use the C and/or operating system functions, where the possible error scenarios are explicit in the API.
@Disch Duoas: it is possible to 're-use' file streams - I see beginners do it successfully all the time in their code.

@dhayden: that's what exceptions are for (even though the C++ streams library doesn't use them), but why on earth would there be a potential for an error to occur on closing a file? Or, when closing anything for that matter? That seems like it could cause huge problems.
Last edited on
/s/Disch/Duoas/
I didn't say it wasn't possible. Reread please.

An error may occur at any time -- even closing a file. For example, the close operation could have been interrupted by a signal.

I am not sure what guarantees the fstream gives for handling errors like that. If it makes a difference whether or not you notice them specifically, then you are stuck using a C style I/O function (and perhaps wrapping it in a custom C++ iostream).

If you just want basic exception handling, either turn it on or use .close() explicitly.

Those are for specific cases though. Normally, you should just let the usual RAII do it's job.
Sorry for the name confusion.

I misread your first sentence - it wasn't followed by 'rather' after the comma so I interpreted it as you believe such functions to not exist at all.

Is a signal really an error condition?

Duoas wrote:
If it makes a difference whether or not you notice them specifically, then you are stuck using a C style I/O function
Duoas wrote:
Those are for specific cases though. Normally, you should just let the usual RAII do it's job.
So...you agree with me?
> that's what exceptions are for (even though the C++ streams library doesn't use them)

See: httptt://en.cppreference.com/w/cpp/io/ios_base/failure
http://en.cppreference.com/w/cpp/io/basic_ios/exceptions

This came with C++11:
std::system_error http://en.cppreference.com/w/cpp/error/system_error (from which std::ios::failure is derived from) holds an associated std::error_code http://en.cppreference.com/w/cpp/error/error_code which has platform specific error codes reported by the operating system and other low-level interfaces like the C stdio library.


A typical scenario where open/close facility on streams is useful:
The program (say, a web server which serves text advertisements) generates a verbose log file.
Periodically (say, one every few minutes), the current log file should be zipped and uploaded to a server which analyses the log files (say for detection and mitigation of click-fraud).

The typical implementation could be:
Close the current log file, rename it with the time stamp.
Open a new log file.
In the background, zip and move the old log file to a directory which is accessible from outside through an ftp server.
I'm aware that you can make streams report errors as exceptions, I meant by default they don't. My mistake for not acknowledging it.

std::system_error is cool, I'm glad that exists.

As for your scenario - wouldn't assigning a new stream to the variable (via move construction) work as well? If not, then I agree that is actually a good use case.
> wouldn't assigning a new stream to the variable (via move construction) work as well?

Yes, it would work (not with the broken GNU library).

But it is significantly more expensive than just closing the stream and then re-opening it (the close/open requests are forwarded to the associated std::filebuf; construction a new std::filebuf as well as the locale and facets, and destruction of the old ones is not required). I wouldn't want to do it in a high performance server program.

Moving streams is useful when the ownership of the stream has to be transferred from one component to another; for instance
std::vector<std::string> parse( std::ifstream file ) ; // the caller does not need the stream any more
or when a vector of streams has to reallocate storage.

I would consider using move for opening and closing a stream a blatant abuse of move semantics. Somewhat akin to:
1
2
3
4
void foo( std::string& str )
{
     str = std::string( "hello" ) ; // this is just plain silly
}
I see, thanks - that's good to know. Still, I think the way it is portrayed in the tutorial is a bit odd.
> I think the way it is portrayed in the tutorial is a bit odd.

The 'Input/output with files' tutorial on this site is not just a bit odd - it is just bad.

I agree that in the vast majority of cases, close and open on a stream would not be a good idea. For instance, among the dozens of posts that I've seen on this forum which uses open/close on a single stream that was reused over and over again, I haven't found even one where that kind of usage was appropriate. Perhaps a whole lot of newbies on this site learnt i/o with files from that tutorial.
I think one of the reasons tutorials and beginners prefer to use functions like open(), close(), is_open(), eof(), etc. is that these functions have verbose names so they are easier to learn. while (!file.eof()) makes sense intuitively, read until we reach the end of the file. Understanding why this is not so good requires a somewhat deeper understanding.
I would prefer if the tutorial didn't even mention those functions - they are documented in the reference, and teaching them in the tutorial has the problems @Peter87 suggested.
JLBorges wrote:
Perhaps a whole lot of newbies on this site learnt i/o with files from that tutorial.
Well, I am at fault for linking to the tutorial, and I know several other people link to it too.

We should probably just rewrite the tutorial and have twicker update it.
This is the one I usually link to: https://www.mochima.com/tutorials/fileIO.html

Not ideal in every respect, but still way better than the one we have on this site.
LB wrote:
We should probably just rewrite the tutorial and have twicker update it.

Isn't Duoas working on an update as a side project? I see that he hasn't updated that part yet, maybe he would appreciate the offer?

The only thing that I can really contribute here is a meager defense of the 'open()' function. Personally code organization is more important to me then some superficial amount of bloat in the library. Personally I like to have all of my variable declarations at the top of the function block, I don't think that there is a functional reason that I am aware of, it just drives me crazy to see people creating variables "as they need them". It seems disorganized like they are only thinking of this line and the next instead of the function as a whole. This means that if for some reason I am to query the user for a file name I would initialize the stream at the top of the function block and call 'open()' after obtaining the string.

@ LB: Have you ever seen the movie Demolition Man? When you make posts like this I am always reminded of a quote from Denis Leary's character in that movie. There's a lot more to it but the relevant part is:
Why? Because maybe I feel the need to okay pal?

Except, without the abrasive context.
Computergeek01 wrote:
Personally I like to have all of my variable declarations at the top of the function block, I don't think that there is a functional reason that I am aware of, it just drives me crazy to see people creating variables "as they need them". It seems disorganized like they are only thinking of this line and the next instead of the function as a whole. This means that if for some reason I am to query the user for a file name I would initialize the stream at the top of the function block and call 'open()' after obtaining the string.
This is a very bad idea. The whole point of RAII in C++ is that you declare variables as you need them. The declaration and initialization should be one and the same. Declaring a variable at the beginning of the function and not initializing it properly until later (maybe even with a condition) is very dangerous: you could accidentally use it uninitialized without realizing it. A variable should never be allowed to be in an uninitialized state: if it isn't ready to be used, it shouldn't be accessible. What you describe is like global variables at function scope level.
Last edited on
You're making assumptions, which is understandable, but you're underestimating the number of mistakes that I've made and (supposedly) learned from. I've made every error you describe in your post and then some; and it's blown up in my face more times then I care to remember. Because of that I set my pointers to NULL, initialize any variables that I can and generally make an effort to be good about it.

My functions are usually outlined before I write them, I don't like to "write on the go" unless I'm studying\experimenting with something.

As for the comment about RAII, you're right you have me there, variables in side a try block for example should exist only in that block when ever appropriate. I suppose saying "block scope" would be more accurate then saying "Function block". That was a misnomer on my part.
but why on earth would there be a potential for an error to occur on closing a file?

close() sync's the stream, so almost anything that can fail on a write() can also fail on a close(). There are also programming errors, such as closing a stream that isn't open.

From the man page for the close() system call on Solaris ( which presumably fstream::close() must call):
ERRORS
The close() function will fail if:

EBADF The fildes argument is not a valid file descriptor.

EINTR The close() function was interrupted by a signal.

ENOLINK
The fildes argument is on a remote machine and the
link to that machine is no longer active.

ENOSPC
There was no free space remaining on the device con-
taining the file.

The close() function may fail if:

EIO An I/O error occurred while reading from or writing to
the file system.

I feel that you would not have "made every error [I] describe" if you "write on the go". To each their own, I guess.

Anyway, Duoas has participated in this thread, so he'll probably see it again later. I don't know if he was only working on the FAQ or if he was also working on revamping the tutorial too.
I want to elaborate on my post. Detecting that an error occurred is only half the battle. It's also important to be able to report the exact reason for an error. Without a reason, it's very hard to figure out how to fix it. With a reason, it's usually trivial.
Pages: 12