Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

La classe QObject

QObject è la classe fondamentale su cui si basa l'architettura del framework Qt: ecco come usarla sfruttando il paradigma della programmazione a eventi.
QObject è la classe fondamentale su cui si basa l'architettura del framework Qt: ecco come usarla sfruttando il paradigma della programmazione a eventi.
Link copiato negli appunti

La classe QObject è la chiave di volta su cui si basa l'architettura del framework Qt. Moltissime delle classi che fanno parte del framework, infatti, derivano in maniera diretta o indiretta da questa classe che incapsula molte delle funzionalità di base per la gestione degli eventi generati dalle widgets.

Poichè una tra le principali funzionalità offerta dal Qt framework è quella di poter creare applicazioni dotate di interfacce grafiche ricche e complesse, la classe QObject implementa moltissime funzionalità proprie della programmazione event-driven. Allo stesso tempo offre anche una gestione semplificata dello heap, sollevando il programmatore dall'onere di implementare le opportune politiche di gestione della memoria senza ricorrere all'uso di un garbage collector.

La classe QObject si presta ad essere usata per composizione o ereditarietà ed espone al programmatore i meccanismi fondamentali su cui verte il modello dati del framework:

  • L'introspezione dei tipi a tempo di esecuzione, a latere al supporto RTTI (run-time type introspection) di C++.
  • Il sistema degli eventi per la comunicazione sincrona e/o asincrona tra varie istanze (inter-object communication).
  • Il sistema delle proprietà.
  • La gestione degli oggetti basata su un meccanismo di composizione gerarchica e ricorsiva.

Il nucleo centrale di tali componenti è un'astrazione propria di Qt nota come Meta-Object System, per la cui implementazione il framework predispone apposite estensioni sintattiche e strumenti di sviluppo.

Il Meta-Object System e l'introspezione dei tipi

In Qt un meta-oggetto è una struttura dati che incapsula i metadati che descrivono un oggetto tradizionale. Tra di essi vi sono il nome della classe di appartenenza, la firma dei metodi che ne fanno parte e le informazioni relative alla gerarchia di classi base. Ogni QObject contiene al suo interno un'istanza della classe QMetaObject che offre una serie di API per l'estrapolazione dei metadati di un'istanza.

La classe QObject a sua volta, espone quindi l'accesso ai metadati dell'istanza mediante il metodo QObject::metaObject(), rendendo quindi possibile effettuare introspezione del tipo a tempo di esecuzione.

Sebbene i metadati di un oggetto siano generalmente di scarso interesse per il programmatore, è proprio la presenza di questa infrastruttura il fattore abilitante per tutte le funzionalità di alto livello del framework.

La creazione dei meta-oggetti è quasi totalmente trasparente al programmatore, cui è richiesto solo di usare alcune macro ed altre estensioni sintattiche di Qt. Tuttavia, da un punto di vista implementativo, l'uso di tali estensioni richiede un passo preliminare alla compilazione di sorgenti per tradurre i costrutti Qt in puro codice C++, al fine di abilitare l'uso di un compilatore standard. Questa procedura è espletata da Moc (Meta-Object Compiler), uno degli strumenti a corredo del framework.

Quando usiamo Qt Creator, l'invocazione di Moc è gestita in autonomia dall'IDE prima di procedere alla compilazione vera e propria. Il risultato di questa fase intermedia è la generazione di sorgenti aggiuntivi, contraddistinti dall'uso del prefisso moc_ nel nome, che contengono il codice necessario per la gestione dei metadati per le classi definite dal programmatore che discendono da QObject.

Segnali e slot: il pattern Observer in Qt

Un'applicazione dotata di interfaccia grafica è caratterizzata dal fatto che l'esecuzione di molte porzioni di codice è condizionata dalle azioni intraprese dall'utente.

In quest'ottica, il comportamento dinamico di un'applicazione dotata di interfaccia grafica è soggetto ad una grande mutabilità poiché dipende da azioni contestuali, ed in quanto tale è necessario garantire che le varie funzionalità associate ad elementi differenti dell'interfaccia confluiscano in maniera armoniosa nel controllo di flusso dell'applicazione, qualunque sia il numero e l'ordine delle azioni effettuate.

La programmazione a eventi è tradizionalmente impiegata per gestire i contesti in cui l'esecuzione del codice è condizionata da fattori esterni allo stato del programma, ad esempio le azioni arbitrarie dell'utente.

In Qt, la programmazione a eventi è implementata applicando uno schema architetturale noto come Observer: un'entità nota come soggetto contiene una lista di altre entità, che in qualche modo dipendono dallo stato del soggetto, dette osservatori. Ogni volta che il soggetto aggiorna il proprio stato lo notifica ai suoi osservatori, i quali, a loro volta, intraprendono opportune azioni correlate al cambiamento di stato.

Nel framework Qt questa astrazione è trasposta mediante l'uso di segnali e slot:

  • Un segnale è un metodo usato da un soggetto per notificare un cambiamento del proprio stato.
  • Uno slot è un metodo di un oggetto osservatore associato alla emissione di un segnale specifico.

Ad esempio, le istanze della classe QPushButton, che implementa la widget con funzionalità di pulsante generico per il framework Qt, emettono il segnale clicked() in corrispondenza della pressione di un tasto del mouse. Una qualsiasi classe derivata da QObject può essere qualificata come osservatrice di un particolare pulsante ed eseguire uno specifico slot per gestire l'evento di click su tale istanza.

La particolarità dell'implementazione dello schema Observer nel framework Qt consiste nel fatto che molti dettagli implementativi dello schema stesso sono resi trasparenti al programmatore mediante l'uso di macro e di altre estensioni alla specifica del linguaggio C++, come ad esempio molte parole chiave, proprie dell'ambiente Qt.

Tale scelta architetturale ha impatto sulla compattezza del codice e sulla leggibilità dello stesso, rendendo l'uso dello schema Observer estremamente semplice ed intuitivo. Il risvolto della medaglia consiste, ancora una volta, nel fatto che per garantire questa funzionalità è necessario ricorrere all'uso di Moc.

Il sistema delle proprietà

Un'altra caratteristica della classe QObject è la possibilità di definire le proprietà di una classe, cioè particolari dati membri cui sono associati una serie di attributi che ne definiscono il comportamento, ad esempio, nel contesto della vista RAD di Qt Creator. Inoltre, è anche possibile aggiungere proprietà ad un oggetto a tempo di esecuzione, come ad esempio avviene per le entità di alcuni linguaggi interpretati come Javascript.

In quest'ultimo caso, la gestione delle proprietà dinamiche si basa su un meccanismo di indicizzazione del tipo chiave-valore, che ancora una volta sfrutta l'infrastruttura offerta dal meta-object system.

Il framework dispone di una serie di macro per la definizione di proprietà statiche e di API apposite per la gestione di quelle dinamiche per le quali si rimanda alla apposita pagina nella documentazione ufficiale.

Composizione gerarchica e ricorsiva degli oggetti Qt

Gli oggetti Qt sono progettati per essere composti dinamicamente in una struttura gerarchica. Ad ogni istanza di QObject infatti può essere associato un oggetto parent al quale sono demandati alcuni aspetti della gestione del ciclo di vita dei propri figli.

In particolare, quando un'istanza di QObject viene deallocata dalla memoria, anche tutti i figli ad essa appartenenti vengono deallocati a loro volta automaticamente, grazie ancora una volta al lavoro svolto da Moc per la creazione e la gestione delle strutture ausiliarie della classe QObject.

In un'epoca antecedente le innovazioni di C++11, soprattutto quelle riguardanti l'uso di smart pointers, questo semplice meccanismo di composizione garantiva sufficiente semplicità nella gestione della memoria prevenendo la possibilità di eventuali leak.

Usare la classe QObject per ereditarietà

La classe QObject si presta ad essere specializzata per vari scopi mediante ereditarietà. La struttura minimale di una classe che deriva da QObject è proposta nel listato seguente.

#include <QObject>
class MyObject : public QObject
{
	Q_OBJECT
public:
	explicit MyObject(QWidget *parent = nullptr);
	~MyObject();
protected:
private:
};

Si noti, in particolare, la presenza della macro Q_OBJECT che a tempo di compilazione, viene espansa da Moc per creare il codice necessario alla definizione delle strutture dati ausiliarie per la gestione dei metadati e la composizione gerarchica dei QObject. La firma del costruttore della classe inoltre ha come argomento opzionale un puntatore all'istanza parent.

Nelle lezioni successive, vedremo come espandere la definizione di una classe con l'aggiunta di segnali e slot per la gestione degli eventi.

Limitazioni e vincoli architetturali

Il modello dati di Qt, essendo estremamente flessibile, è anche molto complesso. Tuttavia, la percezione di tale complessità è attutita dal fatto che buona parte dei dettagli implementativi sono resi invisibili al programmatore grazie al pesante uno di macro e di Moc.

Ciò nonostante, il modello dati di Qt presenta alcune limitazioni e vincoli architetturali che hanno un impatto nella progettazione di applicazioni basate su questo framework. Le principali sono le seguenti:

  • Non si può copiare un QObject. Tra le sue varie funzioni, la macro Q_OBJECT disabilità l'uso del costruttore e dell'operatore di assegnamento di copia. Tale scelta progettuale è logica conseguenza del modo in cui sono organizzati i QObject. Cosa comporterebbe infatti copiare un oggetto insieme alla gerarchia sottostante ed alle associazioni di segnali e slot definite per quella particolare istanza? Probabilmente nulla di buono, rendendo molto oneroso per il programmatore gestire questo scenario. Tale scelta progettuale, si riflette anche nella firma della maggior parte delle API del framework, in cui gli argomenti di tipo QObject o di tipo compatibile, sono sempre passati per riferimento e mai per copia.
  • Particolare cura deve essere posta nell'uso di conversioni. In particolare è necessario prevenire ogni forma di conversione implicita poiché interferirebbe con il meccanismo di composizione gerarchica e gestione dei QObject. Si noti a tal proposito, l'uso di explicit nella dichiarazione dei costruttori di una classe che deriva da QObject.
  • Il tipo QObject è incompatibile con l'uso dei template, principalmente per ragioni implementative più che concettuali. Il sotto sistema dei metadati e la gestione di segnali e slot è infatti fortemente dipendente dall'uso di Moc, che di fatto abilita anche il supporto al controllo statico dei tipi per le classi Qt. Estendere il supporto di Moc alla definizione di classi template che derivano da Qt ne complicherebbe notevolmente l'implementazione e, in una certa misura, sporcherebbe il disegno del framework stesso. Una interessante digressione su questo argomento si trova al seguente link.

Nonostante queste restrizioni possano apparire significative, l'architettura del framework è tale da minimizzare gli effetti penalizzanti che data esse derivano.

Nella pratica, come vedremo meglio nel seguito, i benefici ed i vantaggi che derivano dall'uso consapevole della classe QObject superano di gran lunga le sue limitazioni.


Ti consigliamo anche