In C++, am I paying for what I am not eating?

后端 未结 13 1929
既然无缘
既然无缘 2020-12-12 12:39

Let\'s consider the following hello world examples in C and C++:

main.c

#include 

int main()
{
    printf(\"Hello world\\n\");
    r         


        
13条回答
  •  时光说笑
    2020-12-12 12:42

    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);
    

    Operator Overloading

    is the poster child of how not to use operator overloading:

    std::cout << 2 << 3 && 0 << 5;
    

    Performance

    std::cout is several times slower printf(). The rampant featuritis and virtual dispatch does take its toll.

    Thread Safety

    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.

提交回复
热议问题