C ++ Qt:从线程重定向cout以发出信号
C++ Qt: Redirect cout from a thread to emit a signal
在单个线程中,我有一个漂亮的 class 将所有 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;
};
但是,如果使用 cout 启动任何线程 (QThread),这将不起作用。这是因为所有的指针都被打乱了,必须使用信号和槽来允许子线程和主线程之间的数据传输。
我想修改此 class 以发出信号而不是写入文本文件。这就要求这个 class 变成一个 Q_OBJECT 并且继承自一个。除了 std::basic_streambuf<char>
之外,我还尝试从 QObject
继承,并在正文中添加了 Q_OBJECT 宏,但它没有编译。
你能帮我实现这个吗?我应该怎么做才能让这个 class 发出我可以连接到且线程安全的信号?
推导需要发生QObject
-首先:
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。它至少会阻止您取消对悬挂指针的引用,因为它永远不会悬挂。
对于那些需要完整 "working" 答案的人,请看这里。我只是复制了它,因为@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
在单个线程中,我有一个漂亮的 class 将所有 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;
};
但是,如果使用 cout 启动任何线程 (QThread),这将不起作用。这是因为所有的指针都被打乱了,必须使用信号和槽来允许子线程和主线程之间的数据传输。
我想修改此 class 以发出信号而不是写入文本文件。这就要求这个 class 变成一个 Q_OBJECT 并且继承自一个。除了 std::basic_streambuf<char>
之外,我还尝试从 QObject
继承,并在正文中添加了 Q_OBJECT 宏,但它没有编译。
你能帮我实现这个吗?我应该怎么做才能让这个 class 发出我可以连接到且线程安全的信号?
推导需要发生QObject
-首先:
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。它至少会阻止您取消对悬挂指针的引用,因为它永远不会悬挂。
对于那些需要完整 "working" 答案的人,请看这里。我只是复制了它,因为@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