Comunicare tra i thread in Qt con classi personalizzate

 Premessa:

Il meccanismo dei signals/slots in Qt, come visto nel precedente articolo, è il metodo più semplice e funzionale per comunicare tra i thread. Se la classe che emette il signal e quella che lo riceve tramite slot sono su due thread differenti l’evento viene messo in una coda e quindi processato dal gestore eventi del thread ricevente. Tutti i parametri scambiati tra i signals e gli slots devono però essere conosciuti dalla Qt ed in particolare dal meta-object-system, che si occupa proprio della gestione dei signals/slots.

Creare una classe personalizzata:

Una classe, per essere utilizzata come parametro nei signals/slot collegati tra diversi thread, deve avere come minimo le seguenti caratteristiche:

  • un costruttore pubblico predefinito
  • un costruttore di copia pubblico
  • un distruttore pubblico

Inoltre la classe, per essere riconosciuta dal meta-object-system, deve essere dichiarata e quindi registrata. Come nell’esempio dell’articolo precedente, creiamo un thread che calcola la media tra due numeri, ma in questo caso ogni numero è incorporato in una classe personalizzata che ne contiene anche il nome.

#include <QMetaType>

class CustomNumber
  {
  public:
    CustomNumber(const QString &string=QString(),quint32 value=0);
    CustomNumber(const CustomNumber &other);
    ~CustomNumber();

    QString string(void) const;
    void setString(const QString &string);

    quint32 value(void) const;
    void setValue(quint32 value);

    CustomNumber &operator=(const CustomNumber &other);
    bool operator==(const CustomNumber &other) const;
    bool operator!=(const CustomNumber &other) const;

  public:
    static void registerMetaType(void);

  private:
    QString m_string;
    quint32 m_value;

  };

Q_DECLARE_METATYPE(CustomNumber)

La dichiarazione della classe avviene tramite la macro Q_DECLARE_METATYPE. E’ consigliabile mettere la macro di seguito alla dichiarazione della classe, nello stesso file header. La registrazione della classe deve avvenire a runtime prima di essere utilizzata. La soluzione migliore è quella di creare una funzione pubblica e statica all’interno della classe che registrerà se stessa una volta chiamata dall’esterno.

void CustomNumber::registerMetaType(void)
  {
  if(!QMetaType::type("CustomNumber"))
    {
    qRegisterMetaType<CustomNumber>("CustomNumber");
    }
  }

Chiamando questa funzione siamo sicuri che la classe verrà registrata correttamente ed univocamente.

Utilizzare una classe personalizzata:

Una volta implementata la classe personalizzata è necessario ricordarsi di registrarla prima di utilizzare il meccanismo dei signals/slots. Nel caso dei thread, se si utilizza il metodo spiegato nell’articolo precedente, la strada migliore per registrare la classe è quella di farlo nel costruttore della classe AbstractWorker. In questo modo si è sicuri che la classe personalizzata sia conosciuta al meta-object-system prima di essere utilizzata e nello stesso tempo la registrazione viene fatta una sola volta in un punto ben definito del codice. Se la classe personalizzata è utilizzata in più parti è consigliabile sempre eseguire la registrazione nei costruttori delle classi astratte dei worker in modo che il codice sia perfettamente riutilizzabile senza dipendere da classi esterne.

Conclusioni:

Dichiarando e registrando una classe personalizzata (che deve avere come minimo il costruttore pubblico di default, il costruttore di copia pubblico ed il distruttore pubblico) è possibile scambiare tramite signals e slots qualsiasi tipo di informazione rendendo l’utilizzo dei thread ancora più potente ma nello stesso tempo mantenendo la semplicità e la pulizia del programma.

Esempio completo sviluppato in Qt5

Lascia un commento