【问题标题】:C++ Qt: Redirect cout from a thread to emit a signalC ++ Qt:从线程重定向cout以发出信号
【发布时间】:2015-09-27 19:59:49
【问题描述】:

在单个线程中,我有一个漂亮的类,它将所有 cout 输出重定向到 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;
};

但是,如果任何线程 (QThread) 是使用 cout 启动的,这将不起作用。这是因为所有的指针都搞砸了,必须使用信号和槽来允许子线程和主线程之间的数据传输。

我想修改这个类以发出信号而不是写入文本文件。这要求这个类成为一个 Q_OBJECT 并从一个继承。除了std::basic_streambuf&lt;char&gt; 之外,我还尝试从QObject 继承,并在正文中添加了Q_OBJECT 宏,但它没有编译。

您能帮我实现这一目标吗?我应该怎么做才能让这个类发出我可以连接并且线程安全的信号?

【问题讨论】:

  • std::cout 是一个对象,进程中的所有线程共享该对象,因此“从线程中输出”没有多大意义......
  • 来自非常想使用这样的东西的人:你把这个类叫做哪里?你能举例说明你是如何使用它的吗?
  • @GraemeRock 只需在事件循环或线程的构造函数中调用logStream = new ThreadLogStream(std::cout);。这会将所有发送到std::cout 的内容定向到该类。记住也要实施解决方案。这本身是行不通的。它需要QObject
  • @the-quantum-physicist 我承认,我对代码中的所有省略号和重命名变量感到困惑。是否有可能将带有更正的原始代码作为答案发布?
  • @GraemeRock 对不起,我现在不能这样做。我非常忙。如果你直到周末才开始工作,我会发布该代码。交易?到时候告诉我。

标签: multithreading qt cout qthread


【解决方案1】:

推导需要发生QObject-first:

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

如果目标是最小化修改您的代码,那么有一种更简单的方法。你不需要继承QObject 来发出信号,如果你确切地知道信号要去什么槽。您需要做的就是以线程安全的方式调用插槽:

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

为了加快速度,您可以缓存该方法,这样就不必每次都查找它:

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)") )) {

    ...
  }
};

然后您可以更有效地调用它:

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

最后,当您持有指向其生命周期不受您控制的对象的指针时,使用QPointer 会很有帮助,因为它永远不会悬空。当指向的对象被破坏时,QPointer 将自身重置为 0。它至少会阻止你取消引用一个悬空指针,因为它永远不会悬空。

【讨论】:

  • 我在第一次调用 cout 时遇到了 QMetaObject::invokeMethod 行的分段错误。知道是什么原因造成的吗?
  • @GraemeRock 请提出一个单独的问题,并附上重现问题的完整测试用例。
【解决方案2】:

对于那些需要完整“有效”答案的人,这里就是。我只是复制它,因为@GraemeRock 要求它。

#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

【讨论】:

  • @GraemeRock 这个类是如何在线程中使用的?可以出示相关代码吗?
猜你喜欢
  • 2014-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-15
  • 1970-01-01
  • 2010-10-12
相关资源
最近更新 更多