Saturday 27 February 2016

Why You Shouldn't Use std::endl

There are two terrible habits that I always see in beginner questions on Stack Overflow, that seem to be taught in a lot of books, online tutorials, classes, etc. If your resource is teaching either of these, it may be a sign that you should ditch it, and pick up a good book.
These are:
  • using namespace std;
  • std::cout << "Foo" << std::endl;
    (or worse
    cout << "Foo" << endl
    ).
There are many places to read about why the first is bad, but I’ve lacking a good link for the second. So that’s what I want to talk about here.
Do you use std::endl to end lines when streaming text? Do you know what it does?
std::endl does two things:
  • writes '\n' to the stream.
  • flushes the stream.
And nothing else. I’ve seen people mention that it is the right thing to use to get cross platform line endings. This is just wrong; streaming std::endl is guaranteed to do the same thing as streaming '\n', and platforms make their own guarantees about expanding this into their canonical line endings (for instance, it becomes <CR><LF> on Windows). std::endl exists in the Standard Library only for those situations where you want to both write a newline character and flush the stream. I think, with the benefit of hindsight, this is wrong.
In my mind, these are two entirely unrelated operations. The first is simply writing a character to the target underlying the stream, no more special than any other. The second is an administrative action on the stream object itself, and one that I have rarely seen a good reason to carry out manually. Why would you want to do both at once? Some possibilities:
  • You have some urgent output you want the user to see immediately. Sounds like a perfect case for std::cerr, which has unitbuf set so will display all output immediately and never needs to be manually flushed, entirely for this purpose.
  • You want to display a prompt and make sure it appears before asking for input. std::cout and std::cin are tied together, so this will happen automatically. (Note they are also synced with the C equivalents).
  • You want some sort of live updating output that is not urgent per se, and is not part of user interaction (interleaved with reading). Well it may be the case that for a live updating UI (e.g. top), basic console output is not the best thing, and you should use a toolkit such as ncurses. But let’s say you are just writing a basic example, like this pendulum simulator:
    while (true) {
        std::cout << "Tick" << std::endl;
        sleep(1);
        std::cout << "Tock" << std::endl;
        sleep(1);
    }
    Ok, now I’ve painted myself into a corner where you might legitimately want to flush the stream each time. But the delimiter is still irrelevant to the flushing. What if you decided to separate the ticks with spaces or tabs instead? And anyway, many implementations of std::cout are line-buffered when writing to an actual terminal, for instance libstdc++ (default with gcc), but of course you can’t 100% rely on that and remain completely cross-platform.
So I’ve argued that writing a newline and flushing a stream are unrelated operations, and you rarely want to do the latter anyway. But maybe you do occasionally want to do both at the same time. Isn’t std::endl ideal for that?
I’d still argue no. It’s very important to clearly express your intent in code. Comments are important of course, but it’s even better when they simply aren’t necessary. Comments can be out of date. The code can’t. Now many beginners (and some more experienced programmers, sadly) simply don’t know that std::endl flushes. So when I see it used, I simply have no idea if the original author really intended to flush or not. I see many uses of std::endl where flushing makes absolutely no sense whatsoever, and plenty of uses where it is certainly not clear that flushing is useful.
So what do I recommend? Use '\n', and std::flush if you really do mean it. You may as well put the '\n' into the preceding string literal while you are at it.
std::cout << "foo\n";
std::cout << "Some int: " << i << '\n';
std::cout << "bar\n" << std::flush;
If your printing is a bit convoluted and you really do want to make it clear where you are printing a newline, you can separate it from the preceding string literal, and even give it a name if you like:
namespace cds {
    char const nl = '\n';
}
// ...
std::cout << "Tick" << cds::nl;
Or you can model it on more closely on std::endl:
namespace cds {
    std::ostream& nl(std::ostream& os) {
        return os << '\n';
    }
}
// ...
std::cout << "Tick" << cds::nl;
If you stream a function that takes and returns an std::ostream&, it is called on the stream.
And this is without mentioning the genuine performance problem all the extra flushing can cause. (Dietmar also provides a better nl manipulator, that will work on streams with a character type other than char).

2 comments:

  1. Nice article, mind if I link to this on SO when needed?

    ReplyDelete