reveal: incremental
C++ Streams
Why endl is a Lie
You typed cout << "Hello, World!" << endl; on day one. It worked. You moved on.
Most people never look back. That's the problem.reveal: incremental
The Pipe in the Middle
A stream is simple: bytes flow from your program to somewhere.
That somewhere could be a terminal, a file, a string in memory, a socket.
You don't manage the destination. You just push data in. The stream handles the rest.reveal: incremental
C++ Models It as a Hierarchy
Four objects. Day one.reveal: incremental
The Four Objects
| Object | Direction | Buffered? | Use for |
| ------ | --------- | --------- | --------------- |
| cin | input | yes | reading input |
| cout | output | yes | standard output |
| cerr | output | no | errors |
| clog | output | yes | diagnostics |
Look at that buffered column once again. Remember it.reveal: incremental
Why cout Doesn't Write Immediately
When you do cout << "hello", those bytes don't reach the terminal. Not yet.
They sit in a buffer — a chunk of memory — and wait.
The buffer flushes when it fills up, when you ask it to, or when the program exits cleanly. That's it.reveal: incremental
Why? Because Syscalls Are Expensive
Writing to the terminal is a syscall. You're crossing from user space into the OS. That's expensive.
Doing it once for a thousand lines is fast. Doing it once per line is not.reveal: incremental
<< Chains Because It Returns the Stream
Each call returns the same stream reference:
Clean. Composable. Buffered.reveal: incremental
The endl Lie
Here's the thing nobody tells you early enough:
endl is not a newline character. It's a newline and a forced flush. Every time.reveal: incremental
Here's What It Actually Does
Every endl is a flush. Every flush is a syscall.reveal: incremental
Now Look At This
Version A fires one million syscalls. Version B lets the buffer do its job.
The difference in practice: 10–50×. Same output. One is quietly burning time.reveal: incremental
When endl Is Actually Right
endl = '\n' + flush. Use it when you need output to appear immediately.
These two are identical:
The rule: use endl deliberately, never by habit.reveal: incremental
cin and the Ghost Newline
cin >> skips leading whitespace and stops at the next whitespace. Simple. Until you mix it with getline.reveal: incremental
The Fix
The newline from pressing Enter after the number never got consumed. getline picks it up and calls it a day.
This trips up everyone once. Usually during a contest, at 2am.reveal: incremental
cerr vs clog — Not the Same Thing
Both go to standard error. The difference is one word: buffering.
cerr is unbuffered — if your program crashes on the next line, the message still made it out. clog is buffered — efficient, but not for last-words.
> Crash messages go to cerr. Debug logs go to clog.reveal: incremental
The Two Lines That Make I/O Fast
By default, cout is synchronized with C's printf. That safety has a cost — overhead on every I/O call.
If you're not mixing C and C++ I/O (you probably aren't), turn it off:reveal: incremental
The cin/cout Tie
By default, cin and cout are tied. Before every read from cin, the runtime flushes cout. That's how this works without endl:
cin.tie(nullptr) cuts that connection. In non-interactive programs — files, piped input, competitive programming — there's no user waiting, so the auto-flush is pure overhead.
Together: 3–5× faster I/O in tight loops. Two lines. No downside in modern C++.reveal: incremental
In a Nutshell
A stream is a buffered pipe with state.
Data goes in → sits in the buffer → flushes to the destination when full, on demand, or on exit. The stream remembers if something went wrong.
\n moves the cursor. endl moves the cursor and pays for a syscall.
Use endl deliberately, not by habit.reveal: incremental
C++ Streams
The Toolkit
You know what a stream is. You know why endl costs you. You know the buffer.
Now let's talk about what you can actually do with streams — same pipe, more control.reveal: incremental
Manipulators: Functions in Disguise
You already know one manipulator: endl. It looks like data but it's actually a function call — it takes the stream, does something to it, and returns it.
hex doesn't insert anything. It flips a formatting flag on cout. Every number printed after that comes out in hex — until you change it.
That's the key thing: some manipulators are sticky, some aren't.reveal: incremental
Sticky vs Non-Sticky
Mix these up and your formatting silently breaks two lines later. No warning. No error. Just wrong output.reveal: incremental
Number Formatting
All sticky. Once you switch to hex, every integer prints in hex until you say dec.reveal: incremental
Floating Point
fixed and scientific are sticky. setprecision is sticky. Switch back to defaultfloat when you're done, or every float in your program prints with that precision.reveal: incremental
Width, Fill, Alignment
setfill is sticky. setw is not. A common pattern — set fill once, set width before every insertion.reveal: incremental
Resetting Sticky Manipulators
Rule of thumb: if it starts with show or ends in alpha, there's a no version. Otherwise, set it back to its default value explicitly.reveal: incremental
The Sticky / Non-Sticky Cheatsheet
| Manipulator | Sticky? |
| --------------------- | ------------------- |
| setw | no |
| setprecision | yes |
| setfill | yes |
| hex, oct, dec | yes |
| fixed, scientific | yes |
| left, right | yes |
| boolalpha | yes |
| showbase, showpos | yes |
| endl, flush | — (one-time action) |
When in doubt — assume sticky and reset explicitly.reveal: incremental
File Streams: Same Interface, Different Destination
This is where the stream abstraction pays off. Everything you know about cout and cin works on files. Same operators. Same manipulators. Same state flags.reveal: incremental
Writing
Always check if the file opened. ofstream doesn't throw by default — it just silently fails and every write becomes a no-op.reveal: incremental
Reading
getline and >> work exactly like they do with cin. The same ghost newline trap applies if you mix them.reveal: incremental
Open Modes
Combine modes with |. The most common mistake: forgetting ios::app and wiping a log file on every run.reveal: incremental
RAII — The Destructor Does the Work
You don't need to call out.close() explicitly in most cases. The destructor handles it when the object leaves scope. No leaked file handles, even if an exception fires mid-function.
Call close() explicitly only when you need to reopen the file or check for write errors before the function ends.reveal: incremental
String Streams: Streams in Memory
Sometimes you don't want to write to the terminal or a file. You want to build a string, or parse one. String streams give you the full stream interface — but backed by memory.
Three types: ostringstream (write), istringstream (read), stringstream (both).reveal: incremental
Building Strings
Every manipulator works here too. fixed, setprecision, hex — all of it. This is cleaner than concatenating strings manually, especially when you're mixing types.reveal: incremental
Parsing Strings
Think of it as cin but for a string you already have.
Parse CSV lines, config entries, log records — same >> operator, no file needed.reveal: incremental
Parsing a CSV Line
Swap the delimiter for spaces, then read like normal.reveal: incremental
Type Conversion
A common use case — converting anything to a string and back:
In modern C++ you'd use tostring() and stoi()/stod() for simple cases. But stringstream shines when you need formatting control — precision, bases, padding — during the conversion.reveal: incremental
Reusing a String Stream
str("") clears the buffer. clear() resets the flags. You need both — str("") alone leaves the stream in whatever state it was in.reveal: incremental
When a Stream Breaks
Every stream — cin, cout, ifstream, ofstream, all of them — tracks its own health through four state flags. They live on the base class, so the same flags and the same recovery steps apply everywhere.
The moment something goes wrong, the stream raises a flag and stops reading. Not an exception. Not a crash. Just silence. Every >> after that point is a no-op.reveal: incremental
The Most Common Trigger: Type Mismatch
cin expected digits. Got letters. It sets fail(), leaves "abc" sitting in the buffer, and shuts down. Now this:
Never executes. cin is failed. It doesn't matter what the user types — nothing gets through.reveal: incremental
To Recover, You Need Two Steps
clear() alone isn't enough. The bad input is still in the buffer. The next read picks it up, fails again, and you're back where you started. Both lines, in order.reveal: incremental
bad() Is a Different Beast
It doesn't mean wrong input — it means the stream itself is broken. Underlying hardware failure, corrupted file, OS-level I/O error. The kind of thing you can't recover from in user code.reveal: incremental
The Four Flags — On Every Stream
| Flag | Meaning | Recoverable? |
| -------- | ---------------------------- | ----------------------------------------- |
| good() | all clear, reads will work | — |
| fail() | bad input or format mismatch | yes — clear() + ignore() |
| eof() | end of input reached | sometimes — clear() if more is expected |
| bad() | stream physically corrupted | no |
fail() is the everyday one. bad() is the one you hope never fires.reveal: incremental
The Cheatsheet
| Situation | Use |
| --------------------------- | --------------------------------- |
| End of line, no urgency | "\n" |
| Need output visible now | endl or "\n" << flush |
| Flush without newline | flush |
| High-volume output | "\n" + syncwithstdio(false) |
| Error before possible crash | cerr |reveal: incremental
The Same Pipe, Every Time
Manipulators, file streams, string streams — different tools, same foundation. Learn the abstraction once and the rest follows.
- Assume manipulators are sticky. Reset explicitly when you're done.
- Always check if a file stream opened. Silent failures are worse than loud ones.
- str("") + clear() to reuse a string stream.
- The ghost newline trap from cin applies to istringstream too. Same pipe, same rules.
> The stream doesn't care where it's writing. You just push data in. That's the whole idea.reveal: incremental
Thank You
\n moves the cursor. endl moves the cursor and pays for a syscall.
You've been paying for it since day one. Now you don't have to.
Once again — use endl deliberately, not by habit.