Legacy Approaches Versus Modern Techniques
Understanding Historical Context
In early object-oriented programming practices, software engineers commonly extended system functionality through inheritance. This approach was prevalent in Qt applications where developers would subclass QThread and override the run() function to implement thread behavior.
However, modern software architecture principles strongly favor composition over inheritance. Inheritance should only represent actual "is-a" relationships defined by the requirements, not be abused as a convenient mechanism for code reuse.
Why Inheritance-Based Threading Falls Short
When subclassing QThread, the only meaningful difference lies in the void run() implementation. The interface remains identical across all thread subclasses. This creates several problems:
- The override of
run()serves no genuine architectural purpoce - It couples application logic unnecessarily to the threading framework
QThreadrepresents an operating system thread wrapper- It should serve as a collection of thread operations, not a base for customization
A more flexible approach involves designating the thread entry point dynamically rather than hardcoding it into run().
Improved Threading Model Using Signals and Slots
Core Concept
The solution involves treating QThread as a compositional member rather than a base class. This pattern employs Qt's signals and slots mechanism to define the thread entry point:
- Declare a slot function (such as
execute()) within the worker class - Include a
QThreadmember object in the class - Relocate the current object to the new thread using
moveToThread() - Connect the thread's
started()signal to the execution slot
Implementation: Heap-Allocated Thread Model
This implementation creates the thread object on the heap, allowing explicit lifecycle management:
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QObject>
#include <QThread>
class WorkerThread : public QObject
{
Q_OBJECT
public:
static WorkerThread* createInstance(QObject *parent = nullptr);
void launch();
void stop();
private:
explicit WorkerThread(QObject *parent = nullptr);
~WorkerThread();
QThread* m_workerThread;
QThread* m_previousThread;
protected slots:
void execute();
signals:
void finished();
};
#endif // WORKERTHREAD_H
WorkerThread.h
#include "WorkerThread.h"
#include <QDebug>
WorkerThread::WorkerThread(QObject *parent)
: QObject(parent), m_workerThread(nullptr), m_previousThread(nullptr)
{
m_workerThread = new QThread();
m_previousThread = QThread::currentThread();
moveToThread(m_workerThread);
connect(m_workerThread, SIGNAL(started()), this, SLOT(execute()));
}
WorkerThread* WorkerThread::createInstance(QObject *parent)
{
return new WorkerThread(parent);
}
void WorkerThread::execute()
{
qDebug() << "WorkerThread::execute running on thread:" << QThread::currentThreadId();
for (int counter = 0; counter < 10; counter++)
{
qDebug() << "WorkerThread::execute iteration:" << counter;
}
qDebug() << "WorkerThread::execute completed";
m_workerThread->quit();
moveToThread(m_previousThread);
deleteLater();
}
void WorkerThread::launch()
{
m_workerThread->start();
}
void WorkerThread::stop()
{
m_workerThread->terminate();
}
WorkerThread::~WorkerThread()
{
delete m_workerThread;
qDebug() << "WorkerThread destrucotr executed on:" << QThread::currentThreadId();
}
WorkerThread.cpp
Implementation: Stack-Allocated Thread Model
This simpler variant uses a stack-allocated thread object for scenarios where the thread lifetime matches the containing object's lifetime:
#ifndef EASYTHREAD_H
#define EASYTHREAD_H
#include <QObject>
#include <QThread>
class EasyThread : public QObject
{
Q_OBJECT
public:
explicit EasyThread(QObject *parent = nullptr);
void launch();
void stop();
~EasyThread();
protected slots:
void execute();
private:
QThread m_thread;
};
#endif // EASYTHREAD_H
EasyThread.h
#include "EasyThread.h"
#include <QDebug>
EasyThread::EasyThread(QObject *parent)
: QObject(parent)
{
moveToThread(&m_thread);
connect(&m_thread, SIGNAL(started()), this, SLOT(execute()));
}
void EasyThread::execute()
{
qDebug() << "EasyThread::execute running on thread:" << QThread::currentThreadId();
for (int counter = 0; counter < 10; counter++)
{
qDebug() << "EasyThread::execute iteration:" << counter;
}
qDebug() << "EasyThread::execute completed";
}
void EasyThread::launch()
{
m_thread.start();
}
void EasyThread::stop()
{
m_thread.terminate();
}
EasyThread::~EasyThread()
{
m_thread.wait();
}
EasyThread.cpp
Usage Demonstration
#include <QtCore/QCoreApplication>
#include "EasyThread.h"
#include "WorkerThread.h"
void demonstrateStackThread()
{
EasyThread worker;
worker.launch();
}
void demonstrateHeapThread()
{
WorkerThread* worker = WorkerThread::createInstance();
worker.launch();
}
int main(int argc, char *argv[])
{
QCoreApplication application(argc, argv);
// Uncomment to test either approach
// demonstrateStackThread();
demonstrateHeapThread();
return application.exec();
}
main.cpp
Note: The heap-based implementation contains a subtle race condition. The QThread::quit() function posts a quit event to the thread's event queue rather than terminating immediately. If the worker thread completes before the parent destructor runs, everything functions correctly. However, if the parent destroys the worker first, concurrent access may occur. Proper synchronization mechanisms should be employed for production code.
Understanding deleteLater()
Internal Mechanism
The deleteLater() function posts a deferred deletion event to the object's event queue:
void QObject::deleteLater()
{
QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
}
Execution Context
Key behaviors of deleteLater():
- The deletion event is posted to the event queue of the thread where the object currently resides
- The actual deletion occurs when the event loop processes the deferred delete event
- Execution happens in the thread where
deleteLater()was called - Typically returns to the calling thread for cleanup operations
This mechanism ensures thread-safe cleanup without requiring explicit synchronization in most scenarios.
Summary
Early Qt versions forced developers to subclass QThread for thread creation. Contemporary best practices advocate composition over this inheritance-based approach. Modern Qt applications should:
- Utilize composition to embed
QThreadobjects as members - Employ signals and slots to define flexible thread entry points
- Leverage
moveToThread()for thread affinity management - Consider
deleteLater()for proper resource cleanup
This pattern provides greater flexibility, looser coupling, and improved maintainability compared to traditional inheritance-based threading models.