Qt: шаблон для корректной работы с потоками — более качественная реализация

от автора

В своей предыдущей статье я затронул тему грамотной реализации потоков в Qt и предложил свой вариант. В комментариях мне подсказали более верное направление. Попробовал сделать — получилось и вправду легко и красиво! Я хотел было исправить старую статью, но Хабр повис — и все потерялось. В итоге я решил написать новую версию.

Теперь мы за основу мы возьмем QThread и сделаем от него шаблонного наследника (Шлее реабилитирован!). Подход будет следующий:

  1. создание потока QThread;
  2. в нем в текущем потоке подготавливается информация для нового потока;
  3. клиент вызывает starting (priority)…
  4. … и в переопределенном методе run () — в новом потоке — создается нужный объект, устанавливаются связи, вызывается сигнал «объект готов» и запускается цикл обработки сообщений;
  5. клиент в исходном потоке получает сигнал и новый объект.

Как и в прошлый раз, помним о невозможности 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 и прочих поддерживаемых платформах.

Пример + классы

Файл ThreadedObject:

#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"  

ссылка на оригинал статьи http://habrahabr.ru/post/203254/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *