Collegamento di segnali e slot sovraccarichi in Qt 5


133

Sto riscontrando difficoltà a familiarizzare con la nuova sintassi segnale / slot (utilizzando la funzione puntatore a membro) in Qt 5, come descritto in Nuova sintassi slot segnale . Ho provato a cambiare questo:

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                 slider, SLOT(setValue(int));

a questa:

QObject::connect(spinBox, &QSpinBox::valueChanged,
                 slider, &QSlider::setValue);

ma ricevo un errore quando provo a compilarlo:

errore: nessuna funzione corrispondente per la chiamata a QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Ho provato con clang e gcc su Linux, entrambi con -std=c++11.

Cosa sto sbagliando e come posso ripararlo?


Se la tua sintassi è corretta, l'unica spiegazione potrebbe essere che non stai collegando alle librerie Qt5, ma ad esempio Qt4 invece. Questo è facile da verificare con QtCreator nella pagina "Progetti".
Matt Phillips,

Ho incluso alcune sottoclassi di QObject (QSpinBox ecc.) In modo che avrebbe dovuto includere QObject. Ho provato ad aggiungere anche questo include e comunque non verrà compilato.
dtruby,

Inoltre, sto sicuramente collegando a Qt 5, sto usando Qt Creator e i due kit con cui sto testando hanno entrambi Qt 5.0.1 elencato come versione Qt.
dtruby,

Risposte:


244

Il problema qui è che ci sono due segnali con quel nome: QSpinBox::valueChanged(int)e QSpinBox::valueChanged(QString). Da Qt 5.7, ci sono funzioni di supporto fornite per selezionare il sovraccarico desiderato, in modo da poter scrivere

connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Per Qt 5.6 e precedenti, devi dire a Qt quale vuoi scegliere, lanciandolo nel tipo giusto:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Lo so, è brutto . Ma non c'è modo di aggirare questo. La lezione di oggi è: non sovraccaricare i tuoi segnali e le tue slot!


Addendum : la cosa veramente fastidiosa del cast è quella

  1. uno ripete due volte il nome della classe
  2. bisogna specificare il valore di ritorno anche se di solito void(per i segnali).

Quindi a volte mi sono trovato a usare questo frammento di C ++ 11:

template<typename... Args> struct SELECT { 
    template<typename C, typename R> 
    static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { 
        return pmf;
    } 
};

Uso:

connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)

Personalmente trovo che non sia davvero utile. Mi aspetto che questo problema scompaia da solo quando Creator (o il tuo IDE) inserirà automaticamente il cast giusto quando completerà automaticamente l'operazione di acquisizione del PMF. Ma nel frattempo ...

Nota: la sintassi di connessione basata su PMF non richiede C ++ 11 !


Addendum 2 : in Qt 5.7 sono state aggiunte funzioni di supporto per mitigare questo, modellato sulla mia soluzione alternativa sopra. L'aiutante principale è qOverload(hai anche qConstOverloade qNonConstOverload).

Esempio di utilizzo (dai documenti):

struct Foo {
    void overloadedFunction();
    void overloadedFunction(int, QString);
};

// requires C++14
qOverload<>(&Foo:overloadedFunction)
qOverload<int, QString>(&Foo:overloadedFunction)

// same, with C++11
QOverload<>::of(&Foo:overloadedFunction)
QOverload<int, QString>::of(&Foo:overloadedFunction)

Addendum 3 : se si guarda alla documentazione di qualsiasi segnale sovraccarico, ora la soluzione al problema del sovraccarico è chiaramente indicata nei documenti stessi. Ad esempio, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 dice

Nota: il valore del segnale modificato è sovraccarico in questa classe. Per connettersi a questo segnale utilizzando la sintassi del puntatore a funzione, Qt fornisce un aiuto utile per ottenere il puntatore a funzione come mostrato in questo esempio:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });

1
Ah sì, ha molto senso. Immagino per casi come questo in cui i segnali / slot sono sovraccarichi, mi limiterò a seguire la vecchia sintassi :-). Grazie!
dtruby,

17
Ero così entusiasta della nuova sintassi ... ora una fredda spruzzata di gelida delusione.
RushPL,

12
Per quelli che si chiedono (come me): "pmf" sta per "puntatore alla funzione membro".
Vicky Chijwani,

14
Personalmente preferisco la static_castbruttezza rispetto alla vecchia sintassi, semplicemente perché la nuova sintassi consente un controllo in fase di compilazione per l'esistenza del segnale / slot in cui la vecchia sintassi fallirebbe in fase di esecuzione.
Vicky Chijwani,

2
Sfortunatamente, non sovraccaricare un segnale spesso non è un'opzione: Qt spesso sovraccarica i propri segnali. (es. QSerialPort)
PythonNut

14

Il messaggio di errore è:

errore: nessuna funzione corrispondente per la chiamata a QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

La parte importante di questo è la menzione del " tipo di funzione di sovraccarico irrisolto ". Il compilatore non sa se intendi QSpinBox::valueChanged(int)o QSpinBox::valueChanged(QString).

Esistono diversi modi per risolvere il sovraccarico:

  • Fornire un parametro modello adatto a connect()

    QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
                                             slider,  &QSlider::setValue);

    Questo costringe connect()a risolvere &QSpinBox::valueChangedil sovraccarico che richiede un int.

    Se hai sovraccarichi irrisolti per l'argomento slot, dovrai fornire il secondo argomento template a connect(). Sfortunatamente, non c'è sintassi per chiedere che il primo sia dedotto, quindi dovrai fornire entrambi. Questo è quando il secondo approccio può aiutare:

  • Utilizzare una variabile temporanea del tipo corretto

    void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, signal,
                     slider,  &QSlider::setValue);

    L'assegnazione a signalselezionerà il sovraccarico desiderato e ora può essere sostituita correttamente nel modello. Questo funziona ugualmente bene con l'argomento "slot", e lo trovo meno ingombrante in quel caso.

  • Usa una conversione

    Possiamo evitare static_castqui, poiché è semplicemente una coercizione piuttosto che la rimozione delle protezioni del linguaggio. Uso qualcosa del tipo:

    // Also useful for making the second and
    // third arguments of ?: operator agree.
    template<typename T, typename U> T&& coerce(U&& u) { return u; }

    Questo ci permette di scrivere

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);

8

In realtà, puoi semplicemente avvolgere il tuo slot con lambda e questo:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
    slider, &QSlider::setValue);

sarà migliore. : \


0

Le soluzioni sopra funzionano, ma l'ho risolto in un modo leggermente diverso, usando una macro, quindi nel caso in cui sia:

#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)

Aggiungi questo nel tuo codice.

Quindi, il tuo esempio:

QObject::connect(spinBox, &QSpinBox::valueChanged,
             slider, &QSlider::setValue);

diventa:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);

2
Soluzioni "sopra" cosa? Non dare per scontato che le risposte siano presentate a tutti nell'ordine in cui li stai vedendo!
Toby Speight,

1
Come lo usi per sovraccarichi che accettano più di un argomento? La virgola non causa problemi? Penso che tu abbia davvero bisogno di passare i genitori, cioè #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- usato come CONNECTCAST(QSpinBox, valueChanged, (double))in questo caso.
Toby Speight,

è una bella macro utile quando le parentesi vengono utilizzate per più argomenti, come nel commento di Toby
ejectamenta,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.