2

In a single thread, I have this beautiful class that redirects all cout output to a QTextEdit

#include <iostream>
#include <streambuf>
#include <string>
#include <QScrollBar>

#include "QTextEdit"
#include "QDateTime"

class ThreadLogStream : public std::basic_streambuf<char>, QObject
{
    Q_OBJECT
public:
    ThreadLogStream(std::ostream &stream) : m_stream(stream)
    {
        m_old_buf = stream.rdbuf();
        stream.rdbuf(this);
    }
    ~ThreadLogStream()
    {
        // output anything that is left
        if (!m_string.empty())
        {
            log_window->append(m_string.c_str());
        }

        m_stream.rdbuf(m_old_buf);
    }

protected:
    virtual int_type overflow(int_type v)
    {
        if (v == '\n')
        {
            log_window->append(m_string.c_str());
            m_string.erase(m_string.begin(), m_string.end());
        }
        else
            m_string += v;

        return v;
    }


    virtual std::streamsize xsputn(const char *p, std::streamsize n)
    {
        m_string.append(p, p + n);

        long pos = 0;
        while (pos != static_cast<long>(std::string::npos))
        {
            pos = m_string.find('\n');
            if (pos != static_cast<long>(std::string::npos))
            {
                std::string tmp(m_string.begin(), m_string.begin() + pos);
                log_window->append(tmp.c_str());
                m_string.erase(m_string.begin(), m_string.begin() + pos + 1);
            }
        }

        return n;
    }

private:
    std::ostream &m_stream;
    std::streambuf *m_old_buf;
    std::string m_string;


    QTextEdit* log_window;
};

However, this doesn't work if ANY thread (QThread) is initiated with a cout. This is because all pointers are messed up, and one has to use signals and slots for allowing transfer of data between the sub-thread and the main thread.

I would like to modify this class to emit a signal rather than write to a text file. This requires that this class becomes a Q_OBJECT and be inherited from one. I tried to inherit from QObject in addition to std::basic_streambuf<char> and added Q_OBJECT macro in the body but it didn't compile.

Could you please help me to achieve this? What should I do to get this class to emit signals that I can connect to and that are thread safe?

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • `std::cout` is an object and all threads in a process share that object, so "cout from a thread" doesn't make much sense... – Ulrich Eckhardt Jul 12 '15 at 14:20
  • From someone who would like to use something like this very much: where do you call this class? Can you provide an example of how you use it? – Graeme Rock Dec 05 '16 at 18:16
  • 1
    @GraemeRock Simply in the event loop or the constructor of your thread, call `logStream = new ThreadLogStream(std::cout);`. That will direct everything that goes to `std::cout` to that class. Remember to implement the solution too. This, by itself, doesn't work. It needs `QObject`. – The Quantum Physicist Dec 05 '16 at 18:22
  • @the-quantum-physicist I admit, I am getting confused by all the ellipses and renamed variables in the code. Is there a chance the original code with corrections can be posted as an answer? – Graeme Rock Dec 05 '16 at 18:46
  • @GraemeRock I'm sorry, I can't do that right now. I'm very busy. If you don't get this to working until the weekend, I'll post that code. Deal? Let me know by then. – The Quantum Physicist Dec 05 '16 at 18:50
  • @the-quantum-physicist This is unfortunately still not working. I am getting segmentation errors as described below. So I will take you up on your offer. – Graeme Rock Dec 08 '16 at 19:12
  • @GraemeRock I added it. – The Quantum Physicist Dec 08 '16 at 19:23
  • @TheQuantumPhysicist The `log_window` member variable was removed between the question and the code posted below. How is the signal `sendLogString` connected to the main application ? – SAAD Nov 03 '17 at 14:28
  • @SAAD You connect it yourself with the `ThreadLogStream` object. Use `QObject::connect(...)` etc. – The Quantum Physicist Nov 03 '17 at 14:38

2 Answers2

3

For those who need the full "working" answer, here it's. I just copied it because @GraemeRock asked for it.

#ifndef ThreadLogStream_H
#define ThreadLogStream_H

#include <iostream>
#include <streambuf>
#include <string>
#include <QScrollBar>

#include "QTextEdit"
#include "QDateTime"

class ThreadLogStream : public QObject, public std::basic_streambuf<char>
{
    Q_OBJECT

public:
    ThreadLogStream(std::ostream &stream) : m_stream(stream)
    {
        m_old_buf = stream.rdbuf();
        stream.rdbuf(this);
    }
    ~ThreadLogStream()
    {
        // output anything that is left
        if (!m_string.empty())
        {
            emit sendLogString(QString::fromStdString(m_string));
        }

        m_stream.rdbuf(m_old_buf);
    }

protected:
    virtual int_type overflow(int_type v)
    {
        if (v == '\n')
        {
            emit sendLogString(QString::fromStdString(m_string));
            m_string.erase(m_string.begin(), m_string.end());
        }
        else
            m_string += v;

        return v;
    }


    virtual std::streamsize xsputn(const char *p, std::streamsize n)
    {
        m_string.append(p, p + n);

        long pos = 0;
        while (pos != static_cast<long>(std::string::npos))
        {
            pos = static_cast<long>(m_string.find('\n'));
            if (pos != static_cast<long>(std::string::npos))
            {
                std::string tmp(m_string.begin(), m_string.begin() + pos);
                emit sendLogString(QString::fromStdString(tmp));
                m_string.erase(m_string.begin(), m_string.begin() + pos + 1);
            }
        }

        return n;
    }

private:
    std::ostream &m_stream;
    std::streambuf *m_old_buf;
    std::string m_string;

signals:
    void sendLogString(const QString& str);
};


#endif // ThreadLogStream_H
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • @GraemeRock How is this class used within the thread ? Can you show the relevant code ? – SAAD Nov 03 '17 at 13:15
1

The derivation needs to happen QObject-first:

class LogStream : public QObject, std::basic_streambuf<char> {
  Q_OBJECT
  ...
};
...

If the goal was to minimally modify your code, there's a simpler way. You don't need to inherit QObject to emit signals iff you know exactly what slots the signals are going to. All you need to do is to invoke the slot in a thread safe way:

QMetaObject::invokeMethod(log_window, "append", Qt::QueuedConnection, 
                          Q_ARG(QString, tmp.c_str()));

To speed things up, you can cache the method so that it doesn't have to be looked up every time:

class LogStream ... {
  QPointer<QTextEdit> m_logWindow;
  QMetaMethod m_append;

  LogStream::LogStream(...) :
    m_logWindow(...),
    m_append(m_logWindow->metaObject()->method(
             m_logWindow->metaObject()->indexOfSlot("append(QString)") )) {

    ...
  }
};

You can then invoke it more efficiently:

m_append.invoke(m_logWindow, Qt::QueuedConnection, Q_ARG(QString, tmp.c_str()));

Finally, whenever you're holding pointers to objects whose lifetimes are not under your control, it's helpful to use QPointer since it never dangles. A QPointer resets itself to 0 when the pointed-to object gets destructed. It will at least prevent you from dereferencing a dangling pointer, since it never dangles.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313