⬆️ ⬇️

Qt: template for correct work with threads - better implementation

In my previous article I touched upon the topic of competently implementing threads in Qt and offered my own version. In the comments I suggested a more correct direction. I tried to do it - it turned out really easy and beautiful! I wanted to fix the old article, but Habr was hanging - and everything was lost. In the end, I decided to write a new version.



Now we take QThread as a basis and make a template heir from it ( Schlee rehabilitated!). The approach will be as follows:

  1. creating a QThread thread;
  2. it prepares information for the new flow in the current flow;
  3. the client calls starting (priority) ...
  4. ... and in the overridden run () method - in the new stream - the necessary object is created, connections are established, the "object ready" signal is called and the message processing cycle is started;
  5. The client in the original stream receives a signal and a new object.


Like last time, we recall the inability of the MOC to handle the template class : “MOC does not allow to use all the features of C ++. The main problem is that class templates cannot have signals or slots . ”



Implementation



Consider the code of the created classes (so that it all fits into the screen, I removed the comments):



// ** // **     // ** 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 (); } }; 


')

Here the main method is starting, which memorizes the names of signals and slots, and also sets the pending removal of the method. The objectIsCreated () method returns true when an object has already been created. Multiple overloads allow you to use ThreadedObject <T> as a smart pointer.



Here is a simple example of using these classes:



 ThreadedObject <Operation> _obj; QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ())); _obj.starting (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority); 




A real example is attached below - a button is created in the main thread. In the new thread, an int variable is created, as well as a signal from the timer and a timer event. Both of these timers reduce the value of the variable int, when the value reaches zero, the QCoreApplication :: quit () slot is called. On the other hand, closing the application stops the flow. The example is verified in WinXP. I would like to hear in the comments about the successful trials in Linux, MacOS, Android and other supported platforms .



Example + Classes
ThreadedObject file:



 #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 file:



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


Source: https://habr.com/ru/post/203254/



All Articles