В своей предыдущей статье я затронул тему грамотной реализации потоков в Qt и предложил свой вариант. В комментариях мне подсказали более верное направление. Попробовал сделать — получилось и вправду легко и красиво! Я хотел было исправить старую статью, но Хабр повис — и все потерялось. В итоге я решил написать новую версию.
Теперь мы за основу мы возьмем QThread и сделаем от него шаблонного наследника (Шлее реабилитирован!). Подход будет следующий:
- создание потока QThread;
- в нем в текущем потоке подготавливается информация для нового потока;
- клиент вызывает starting (priority)...
- … и в переопределенном методе run () — в новом потоке — создается нужный объект, устанавливаются связи, вызывается сигнал «объект готов» и запускается цикл обработки сообщений;
- клиент в исходном потоке получает сигнал и новый объект.
Как и в прошлый раз, помним о невозможности MOCом обработать шаблонный класс: «MOC не позволяет использовать все возможности С++. Основная проблема в том, что шаблоны классов не могут иметь сигналы или слоты».
Реализация
Рассмотрим код созданных классов (чтобы оно все влезло в экран, я убрал комментарии):
// **
// ** Базовый класс для потока
// **
class ThreadedObjectBase: public QThread
{
Q_OBJECT
protected:
const char *_finished_signal;
const char *_terminate_slot;
bool _to_delete_later_object;
void initObject (QObject *obj)
{
bool res;
if (_finished_signal)
{
res = connect (obj, _finished_signal, this, SLOT (quit ()));
Q_ASSERT_X (res, "connect", "connection is not established");
}
if (_terminate_slot)
{
res = connect (this, SIGNAL (finished ()), obj, _terminate_slot);
Q_ASSERT_X (res, "connect", "connection is not established");
}
if (_to_delete_later_object && _finished_signal)
{
res = connect (obj, _finished_signal, obj, SLOT (deleteLater ()));
Q_ASSERT_X (res, "connect", "connection is not established");
}
emit objectIsReady ();
}
public:
ThreadedObjectBase (QObject *parent = 0): QThread (parent),
_finished_signal (0), _terminate_slot (0), _to_delete_later_object (true) {}
signals:
void objectIsReady (void);
};
// **
// ** Шаблонный класс для потока
// **
template <class T>
class ThreadedObject: public ThreadedObjectBase
{
protected:
T *_obj;
public:
ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0) {}
void starting (
const char *FinishedSignal = 0,
const char *TerminateSlot = 0,
QThread::Priority Priority = QThread::InheritPriority,
bool ToDeleteLaterThread = true,
bool ToDeleteLaterObject = true)
{
_finished_signal = FinishedSignal;
_terminate_slot = TerminateSlot;
_to_delete_later_object = ToDeleteLaterObject;
start (Priority);
}
void run (void) { initObject (_obj = new T); exec (); }
bool objectIsCreated (void) const { return _obj != 0; }
T* ptr (void) { return reinterpret_cast <T*> (_obj); }
const T* cptr (void) const { return reinterpret_cast <const T*> (_obj); }
operator T* (void) { return ptr (); }
T* operator -> (void) { return ptr (); }
operator const T* (void) const { return cptr (); }
const T* operator -> (void) const { return cptr (); }
};
Тут основной метод — starting, который запоминает имена сигналов и слотов, а также устанавливает отложенное удаление метода. Метод objectIsCreated () возвращает истину когда объект уже создан. Многочисленные перегрузки позволяют использовать ThreadedObject<T> как «умный» указатель.
Вот простенький пример использования этих классов:
ThreadedObject <Operation> _obj;
QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ()));
_obj.starting (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority);
Снизу прилагается реальный пример — в основном потоке создается кнопка. В новом потоке создается переменная типа int, а также сигнал от таймера и событие по таймеру. Оба этих таймера уменьшают значение переменной int, по достижению нуля вызывается слот QCoreApplication::quit (). С другой стороны, закрытие приложения останавливает поток. Пример проверен в WinXP. Хотелось бы в комментариях услышать об успешных испытаниях в Linux, MacOS, Android и прочих поддерживаемых платформах.
#include <QtCore>
// **
// ** Базовый класс для потока
// **
class ThreadedObjectBase: public QThread
{
Q_OBJECT
protected:
const char *_finished_signal; // имя сигнала "окончание работы объекта"
const char *_terminate_slot; // имя слота "остановка работы"
bool _to_delete_later_object; // установка отложенного удаление объекта
// . настройка
void initObject (QObject *obj)
{
bool res;
if (_finished_signal) // установить сигнал "окончание работы объекта"?
{ res = connect (obj, _finished_signal, this, SLOT (quit ())); Q_ASSERT_X (res, "connect", "connection is not established"); } // по окончанию работы объекта поток будет завершен
if (_terminate_slot) // установить слот "остановка работы"?
{ res = connect (this, SIGNAL (finished ()), obj, _terminate_slot); Q_ASSERT_X (res, "connect", "connection is not established"); } // перед остановкой потока будет вызван слот объекта "остановка работы"
if (_to_delete_later_object && _finished_signal) // установить отложенное удаление объекта?
{ res = connect (obj, _finished_signal, obj, SLOT (deleteLater ())); Q_ASSERT_X (res, "connect", "connection is not established"); } // по окончанию работы объекта будет установлено отложенное удаление
emit objectIsReady (); // объект готов к работе
}
public:
ThreadedObjectBase (QObject *parent = 0): QThread (parent){}
signals:
void objectIsReady (void); // сигнал "объект запущен"
}; // class ThreadedObject
// **
// ** Шаблонный класс для потока
// **
template <class T>
class ThreadedObject: public ThreadedObjectBase
{
protected:
T *_obj; // объект, исполняемый в новом потоке
public:
ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0) {}
// . настройка
void starting (const char *FinishedSignal = 0, const char *TerminateSlot = 0, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true, bool ToDeleteLaterObject = true) // запуск нового потока
{
_finished_signal = FinishedSignal; // запоминание имени сигнала "окончание работы объекта"
_terminate_slot = TerminateSlot; // запоминание имени слота "остановка работы"
_to_delete_later_object = ToDeleteLaterObject; // запоминание установки отложенного удаление объекта
start (Priority); // создание объекта
}
void run (void) { initObject (_obj = new T); exec (); } // создание объекта
// . состояние
bool objectIsCreated (void) const { return _obj != 0; } // объект готов к работе?
T* ptr (void) { return reinterpret_cast <T*> (_obj); } // указатель на объект
const T* cptr (void) const { return reinterpret_cast <const T*> (_obj); } // указатель на константный объект
// . перегрузки
operator T* (void) { return ptr (); } // указатель на объект
T* operator -> (void) { return ptr (); } // указатель на объект
operator const T* (void) const { return cptr (); } // указатель на константный объект
const T* operator -> (void) const { return cptr (); } // указатель на константный объект
}; // class ThreadedObject
Файл main.cpp:
#include <QtGui>
#include <QtWidgets>
#include <QtCore>
#include "ThreadedObject.h"
// **
// ** Выполнение операции
// **
class Operation: public QObject
{
Q_OBJECT
int *Int; // некоторая динамическая переменная
QTimer _tmr; // таймер
int _int_timer; // внутренний таймер
public:
Operation (void) { Int = new int (5); } // некоторый конструктор
~Operation (void) { if (Int) delete Int; } // некоторый деструктор
signals:
void addText(const QString &txt); // сигнал "добавление текста"
void finished (); // сигнал "остановка работы"
public slots:
void terminate () // досрочная остановка
{
killTimer (_int_timer); // остановка внутреннего таймера
_tmr.stop (); // остановка внешенго таймера
delete Int; // удаление переменной
Int = 0; // признак завершения работы
emit finished (); // сигнал завергения работы
}
void doAction (void) // некоторое действие
{
bool res;
emit addText (QString ("- %1 -"). arg (*Int));
res = QObject::connect (&_tmr, &QTimer::timeout, this, &Operation::timeout); Q_ASSERT_X (res, "connect", "connection is not established"); // связывание внешнего таймера
_tmr.start (2000); // запуск внешнего таймера
thread()->sleep (1); // выжидание 1 сек...
timeout (); // ... выдача состояния ...
startTimer (2000); // ... и установка внутреннего таймера
}
protected:
void timerEvent (QTimerEvent *ev) { timeout (); } // внутренний таймер
private slots:
void timeout (void)
{
if (!Int || !*Int) // поток закрывается?
return; // ... выход
--*Int; // уменьшение счетчика
emit addText (QString ("- %1 -"). arg (*Int)); // выдача значения
if (!Int || !*Int) // таймер закрыт?
emit finished (); // ... выход
}
};
// **
// ** Объект, взаимодействующий с потоком
// **
class App: public QObject
{
Q_OBJECT
ThreadedObject <Operation> _obj; // объект-поток
QPushButton _btn; // кнопка
protected:
void timerEvent (QTimerEvent *ev)
{
bool res; // признак успешности установки сигналов-слотов
killTimer (ev->timerId ()); // остановка таймера
res = QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ())); Q_ASSERT_X (res, "connect", "connection is not established"); // установка связей с объектом
_obj.starting (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority); // запуск потока с высоким приоритетом
}
private slots:
void setText (const QString &txt) { _btn.setText (txt); } // установка надписи на кнопке
void connectObject (void) // установка связей с объектом
{
bool res; // признак успешности установки сигналов-слотов
res = QObject::connect (this, &App::finish, _obj, &Operation::terminate); Q_ASSERT_X (res, "connect", "connection is not established"); // закрытие этого объекта хакрывает объект в потоке
res = QObject::connect (this, &App::startAction, _obj, &Operation::doAction); Q_ASSERT_X (res, "connect", "connection is not established"); // установка сигнала запуска действия
res = QObject::connect (_obj, &Operation::finished, this, &App::finish); Q_ASSERT_X (res, "connect", "connection is not established"); // конец операции завершает работу приложения
res = QObject::connect (_obj, &Operation::addText, this, &App::setText); Q_ASSERT_X (res, "connect", "connection is not established"); // установка надписи на кнопку
res = QObject::connect (&_btn, &QPushButton::clicked, _obj, &Operation::terminate); Q_ASSERT_X (res, "connect", "connection is not established"); // остановка работы потока
_btn.show (); // вывод кнопки
emit startAction (); // запуск действия
}
public slots:
void terminate (void) { emit finish (); } // завершение работы приложения
signals:
void startAction (void); // сигнал "запуск действия"
void finish (void); // сигнал "завершение работы"
};
// **
// ** Точка входа в программу
// **
int main (int argc, char **argv)
{
QApplication app (argc, argv); // приложение
App a; // объект
bool res; // признак успешности операции
a.startTimer (0); // вызов функции таймера объекта при включении цикла обработки сообщений
res = QObject::connect (&a, SIGNAL (finish ()), &app, SLOT (quit ())); Q_ASSERT_X (res, "connect", "connection is not established"); // окончание работы объекта закрывает приложение
res = QObject::connect (&app, SIGNAL (lastWindowClosed ()), &a, SLOT (terminate ())); Q_ASSERT_X (res, "connect", "connection is not established"); // окончание работы приложения закрывает объект
return app.exec(); // запуск цикла обработки сообщений
}
#include "main.moc"
Автор: avn