Let\'s consider the following hello world examples in C and C++:
main.c
#include
int main()
{
printf(\"Hello world\\n\");
r
You are paying for a mistake. In the 80s, when compilers aren't good enough to check format strings, operator overloading was seen as a good way to enforce some semblance of type safety during io. However, every one of its banner features are either implemented badly or conceptually bankrupt from the start:
The most repugnant part of the C++ stream io api is the existence of this formatting header library. Besides being stateful and ugly and error prone, it couples formatting to the stream.
Suppose you want to print out an line with 8 digit zero filled hex unsigned int followed by a space followed by a double with 3 decimal places. With
, you get to read a concise format string. With
, you have to save the old state, set alignment to right, set fill character, set fill width, set base to hex, output the integer, restore saved state (otherwise your integer formatting will pollute your float formatting), output the space, set notation to fixed, set precision, output the double and the newline, then restore the old formatting.
//
std::printf( "%08x %.3lf\n", ival, fval );
// &
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);
is the poster child of how not to use operator overloading:
std::cout << 2 << 3 && 0 << 5;
std::cout
is several times slower printf()
. The rampant featuritis and virtual dispatch does take its toll.
Both
and
are thread safe in that every function call is atomic. But, printf()
gets a lot more done per call. If you run the following program with the
option, you will see only a row of f
. If you use
on a multicore machine, you will likely see something else.
// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp
#define USE_STREAM 1
#define REPS 50
#define THREADS 10
#include
#include
#if USE_STREAM
#include
#else
#include
#endif
void task()
{
for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
std::cout << std::hex << 15 << std::dec;
#else
std::printf ( "%x", 15);
#endif
}
int main()
{
auto threads = std::vector {};
for ( int i = 0; i < THREADS; ++i )
threads.emplace_back(task);
for ( auto & t : threads )
t.join();
#if USE_STREAM
std::cout << "\n\n";
#else
std::printf ( "\n\n" );
#endif
}
The retort to this example is that most people exercise discipline to never write to a single file descriptor from multiple threads anyway. Well, in that case, you'll have to observe that
will helpfully grab a lock on every <<
and every >>
. Whereas in
, you won't be locking as often, and you even have the option of not locking.
expends more locks to achieve a less consistent result.