Nested structs as signal arguments in Qt
Worker objects in Qt are used to offload lengthy operations
onto separate threads, out of the main event loop. They usually operate on data
that already exists, e.g. building a palette from a QBitmap
, or counting words
in a QTextEdit
.
However, there also exists another class of usages, where the worker object is tasked with producing some data, basically out of the ether. This happened to me just today: I wanted to pre-process a video to calculate its duration, resolution, and also average and maximum bit-rates. The most natural way to return that info would be to emit it inside a signal, so I wrote:
#include <QObject>
#include <QSize>
#include <chrono>
class Worker : public QObject
{
Q_OBJECT
QString m_inputFilepath;
public:
struct Result {
QSize resolution;
std::chrono::milliseconds duration {0};
double avgBitrateMbps = -1.0;
double maxBitrateMbps = -1.0;
};
explicit Worker(QObject *parent = nullptr) {
qRegisterMetaType<Result>();
}
void setInputFilepath(QString filepath);
public slots:
void process();
signals:
void finished(Result);
};
Q_DECLARE_METATYPE(Worker::Result)
This compiled without a warning—and I’m pretty thorough with those, enabling
-Wall
and -Wextra
, and turning them into errors with -Werror
. So I went on
to use it:
auto thread = new QThread();
auto worker = new Worker();
->setInputFilepath(m_videoFilepath);
worker->moveToThread(thread);
workerconnect(thread, &QThread::started, worker, &Worker::process);
connect(worker, &Worker::finished, this, &MainWindow::storeVideoInfo);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(worker, &Worker::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
->start(); thread
Again, it compiled—no warnings, no errors. But when I ran the program…
QObject::connect: Cannot queue arguments of type 'Result'
(Make sure 'Result' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'Result'
(Make sure 'Result' is registered using qRegisterMetaType().)
Uh-oh! What’s going on? Clearly I did register my type, it’s right there in
Worker
’s constructor…
As is usual with thorniest of Qt problems, this one is related to Moc—the
program that implements signals and slots. It processes headers with Q_OBJECT
in them, and generates additional files (like moc_worker.cpp) that implement
Qt’s internals.
This particular issue is a limitation in Moc. QObject::connect
compares types as strings, and it’s completely unaware of scopes. In my listings
above, it doesn’t understand that Result
in void finished(Result)
and the
Worker::Result
throughout the rest of the code is one and the same type.
To fix this, fully qualify the type in signal’s definition:
void finished(Worker::Result);
Your thoughts are welcome by email
(here’s why my blog doesn’t have a comments form)