In termini di standard C++, la soluzione Qt funziona altrettanto bene.
emettere un segnale viene fatto chiamando il metodo:
emit someSignal(3.14);
Il emit
parola effettivamente risolve ad un vuoto #define
, quindi la linea sopra invoca semplicemente il metodo someSignal
con gli argomenti dati. Il metodo potrebbe essere stata dichiarata all'interno di una classe -derived QObject
in questo modo:
class SomeObject: public QObject {
Q_OBJECT
public slots:
void firstSlot() { /* implementation */ }
void secondSlot(double) { /* implementation */ }
signals:
void someSignal(double); /* no implementation here */
};
Questo dovrebbe sembrare familiare a voi, ma si potrebbe avere chiesti dove l'effettiva attuazione dei vostri segnali proviene. Come si può immaginare, questo è dove meta oggetto compilatore Qt (MOC) entra in gioco Per ogni metodo dichiarato all'interno della sezione signals
fornisce nella sua sorgente generato un implementazione che assomiglia più o meno in questo modo:.
void SomeObject::someSignal(double _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
La parte interessante è il vettore void *_a[]
riempito con puntatori agli argomenti passati al segnale. Niente di particolare qui.
Il vettore dell'argomento viene passato a QMetaObject::activate
, che a sua volta esegue alcuni controlli di sicurezza dei thread e altre operazioni di manutenzione e quindi inizia a chiamare gli slot che sono stati collegati al segnale, se presenti. Poiché le connessioni segnale-area sono risolte in fase di esecuzione (il modo in cui funziona connect()
), è necessario un nuovo aiuto da parte di MOC. In particolare, MOC genera anche un qt_static_metacall()
implementazione tua classe:
void SomeObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
SomeObject *_t = static_cast<SomeObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->firstSlot(); break;
case 1: _t->secondSlot((*reinterpret_cast< double(*)>(_a[1]))); break;
default: ;
}
} /* some more magic */
}
Come si può vedere, questo metodo contiene l'altra estremità per risolvere il void *_a[]
vettore da prima a una chiamata di funzione. Inoltre puoi vedere che non c'è nessun elenco di argomenti variadici (usando i puntini di sospensione, ...
) o altri trucchi discutibili coinvolti.
Quindi, per chiarire la domanda originale: quando, ad es.someSignal(double)
è collegato allo secondSlot(double)
che corrisponde alla firma del segnale, la chiamata si risolve in case 1
nello qt_static_metacall
e passa semplicemente l'argomento come previsto.
Quando si collega il segnale a firstSlot()
con meno argomenti rispetto al segnale, la chiamata viene risolta in case 0
e firstSlot()
viene richiamato senza argomenti. L'argomento che è stato passato al segnale rimane semplicemente intatto nel suo vettore void *_a[]
.
Questo è anche conforme a qualsiasi standard C++ o è una funzione garantita per Qt? Almeno in bianco C, questo porta a UB tramite ISO/IEC 9899: TC2 §6.5.2.2p6. – Kamajii
@Kamajii: le chiamate di slot non sono chiamate di funzione diretta. Non c'è UB, Qt si occupa di chiamare correttamente la funzione. – Mat
Ovviamente le invocazioni di slot sono solo chiamate regolari (connessioni + -queued, ecc.). Quando l'emissione del segnale raggiunge finalmente il membro autogenerato (moc) 'qt_static_metacall', sorge il problema. – Kamajii