0

I am trying to control the output prints in my simulation. It prints a lot of output stream information. This is a sample code of how I try to control the output stream. Sometimes I want to print information for each thread and sometimes I do not want a single print from threads to reduce the system calls in the simulation. I pass command line argument to control the stream. Argument v means no prints. The problem is it requires a lot of if statements in whole simulator. Is there any easy way to deal with this issue?

#include <iostream>
#include <thread>

void work_to_do_1(char ch)
{
//work for thread 1
if(ch != 'v')
std::cout << "-:Thread 1:-" << std::endl;
}

void work_to_do_2(char ch)
{
if (ch != 'v')
std::cout << "-:Thread 2:-" << std::endl;
}

void work_to_do_3(char ch)
{
if (ch != 'v')
std::cout << "-:Thread 3:-" << std::endl; 
}

int main(int argc, char *arg[])
{
std::cout << "You have entered " << argc
    << " arguments:" << "\n";

for (int i = 0; i < argc; ++i)
{
    std::cout << arg[i] << "\n";
}
char t = *arg[1];
std::cout << "manager is running" << std::endl;

std::thread t1(work_to_do_1, t);
std::thread t2(work_to_do_2, t);
std::thread t3(work_to_do_3, t);
t1.join();
t2.join();
t3.join();
system("pause");
return 0;
}
Zeeshan Hayat
  • 401
  • 6
  • 13

2 Answers2

0

Make your own nul stream:

struct cnul_t : std::basic_ostream<char> {} cnul;
template<class T> std::ostream& operator<<(cnul_t& os, T const&) { return os; }

And redirect your output to it to ignore it:

#include <ostream>
#include <iostream>

struct cnul_t : std::basic_ostream<char> {} cnul;
template<class T> std::ostream& operator<<(cnul_t& os, T const&) { return os; }

void maybe_log(bool b)
{
    std::ostream& out = b == true ? std::cout : cnul;
    out << "Hello, World!\n";
}

int main()
{
    maybe_log(true);  // outputs Hello, World!
    maybe_log(false); // no output
}

Demo: http://coliru.stacked-crooked.com/a/362ecb660283cbff

YSC
  • 38,212
  • 9
  • 96
  • 149
0

OK, well, if you have read and understood the comments you will see that the real problem is not what you think it is. The real problem is that your logging code is not threadsafe.

This answer explains the problem very well. Although ostreams are threadsafe in themselves (since C++11), something like std::cout << "-:Thread 1:-" << std::endl; is actually two calls to std::cout.operator<< and another thread might sneak in between them thus garbling your output. This, I imagine, you could do without.

So, stealing code unashamedly from this post I humbly submit the following solution (which also has a global flag, gLogging, to turn logging on or off). This will write lines to std::cout atomically whenever you log std::endl. I wrote this as an exercise to develop my own personal skills and I thought you might like to have it.

See the linked post for an explanation of how std::endl is detected, but the underlying principle is a separate log buffer for each thread which is flushed to std::cout when it has a complete line of output to get rid of. The code includes a manager class (Logger) to take care of the details of creating, destroying and accessing these buffers. You just need to put two lines of initialisation code at the start of each thread as shown and then log to logstream rather than std::cout.

#include <iostream>
#include <sstream>
#include <mutex>
#include <map>
#include <thread>

bool gLogging = true;
constexpr int bufsize = 512;        // needs to be big enough for longest logging line expected

// A streambuf that writes atomically to std::cout when (indirectly) it sees std::endl
class LogBuf : public std::stringbuf
{
public:
     LogBuf () { setbuf (m_buf = new char [bufsize], bufsize); str (""); }
     ~LogBuf () { delete [] m_buf; }

protected:
     // This gets called when the ostream we are serving sees endl
     int sync() override
     {
         if (gLogging)
         {
             std::cout << str();
             std::cout.flush();
         }
         str("");
         return 0;
     }

private:
    char *m_buf;
};

// An ostream that uses LogBuf
class LogStream : public std::ostream
{
public:
    LogStream () : std::ostream (m_LogBuf = new LogBuf ()) { }
    ~LogStream () { delete m_LogBuf; }

private:
    LogBuf *m_LogBuf;
};

// A class to manage LogStream objects (one per thread)
class Logger
{
public:
    void AddThread (void)
    {
        mutex.lock ();
        m_logstreams [std::this_thread::get_id ()] = new LogStream ();
        mutex.unlock ();
    }

    void RemoveThread ()
    {
        mutex.lock ();
        std::thread::id thread_id = std::this_thread::get_id ();
        LogStream *logstream = m_logstreams [thread_id];
        m_logstreams.erase (m_logstreams.find (thread_id));
        mutex.unlock ();
        delete logstream;
    }

    LogStream& GetLogStream ()
    {
        mutex.lock ();
        LogStream *logstream = m_logstreams [std::this_thread::get_id ()];
        mutex.unlock ();
        return *logstream;
    }

    private:
    static std::mutex mutex;
    std::map<const std::thread::id, LogStream *> m_logstreams;
};

std::mutex Logger::mutex;
Logger logger;

// A simple class to make sure we remember to call RemoveThread
class LogStreamHelper
{
public:
    LogStreamHelper () { logger.AddThread (); }
    ~LogStreamHelper () { logger.RemoveThread (); }
    inline LogStream &GetLogStream () { return logger.GetLogStream (); }
};

// Test program
void work_to_do_1()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "-:Thread 1:-" << std::endl;
}

void work_to_do_2()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "-:Thread 2:-" << std::endl;
}

int main ()
{
    LogStreamHelper logstream_helper;
    LogStream& logstream = logstream_helper.GetLogStream ();
    logstream << "Main thread" << std::endl;

    std::thread t1 (work_to_do_1);
    std::thread t2 (work_to_do_2);

    t1.join ();
    t2.join ();

    return 0;
}

Output:

Main thread
-:Thread 1:-
-:Thread 2:-

Run it at Wandbox.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thanks, I know that the code is not thread safe it will give results not in a proper way but the problem under consideration is just to control the output (turn it on or off depending upon the argument passed). – Zeeshan Hayat Jun 04 '18 at 18:31
  • You said you wanted to log in sub-threads. I've given you the tools, why don't you do it properly? – Paul Sanders Jun 04 '18 at 18:32
  • True, each thread is printing something and I am just trying to control the output stream. The issue is that if I have 100 threads and each thread has multiple different classes, it is too much work to initialize logger everywhere. I am just looking for a solution that is scalable :) – Zeeshan Hayat Jun 04 '18 at 18:41
  • Well it's your call, but just to be clear, `LoggerHelper` only needs to be instantiated once per thread, and then to stay in scope until the thread exits. And in fact, I made a mistake. `LogStreamHelper:: GetLogStream ()` can be made `static` which means you can call it in any thread, at any time, just so long as you have created an instance of `LoggerHelper` right at the start of each thread. So you could do: `#define LOGGER_OUT LogStreamHelper:: GetLogStream ()` and then `LOGGER_OUT << "foo" << std::endl;` anywhere in your code and be properly threadsafe. – Paul Sanders Jun 04 '18 at 18:51