2013-11-01 21 views
31

Ho un oggetto che QAction inizializzo come segue:Qt Slot e C++ 11 lambda

QAction* action = foo->addAction(tr("Some Action")); 
connect(action, SIGNAL(triggered()), this, SLOT(onSomeAction())); 

E poi onSomeAction sembra qualcosa di simile:

void MyClass::onSomeAction() 
{ 
    QAction* caller = qobject_cast<QAction*>(sender()); 
    Q_ASSERT(caller != nullptr); 

    // do some stuff with caller 
} 

Questo funziona bene, ho la caller oggetto indietro e sono in grado di usarlo come previsto. Poi provo modo C++ 11 per collegare l'oggetto come ad esempio:

connect(action, &QAction::triggered, [this]() 
{ 
    QAction* caller = qobject_cast<QAction*>(sender()); 
    Q_ASSERT(caller != nullptr); 

    // do some stuff with caller 
}); 

Ma caller è sempre nullo e quindi i Q_ASSERT trigger. Come posso usare lambda per ottenere il mittente?

+1

duplicati di http://stackoverflow.com/questions/14717755/how-to-a ccess-qobjectsender-in-a-c-lambda –

+0

@FrankOsterfeld: Non proprio, dato che qui non hai bisogno di alcuna ginnastica come nel presunto duplicato. Qui è una questione banale catturare il mittente esplicitamente disponibile dato a 'connect' come argomento. –

risposta

53

La semplice risposta è: non è possibile. O, piuttosto, non vuoi (o non hai bisogno!) Di usare sender(). È sufficiente acquisire e utilizzare action.

//        Important! 
//         vvvv 
connect(action, &QAction::triggered, this, [action, this]() { 
    // use action as you wish 
    ... 
}); 

La specifica this come il contesto oggetto per funtore assicura che il funtore non verrà invocata se sia l'azione o this (a QObject) cessano di esistere. Altrimenti, il functor proverebbe a fare riferimento a puntatori penzolanti.

In generale, il seguente deve tenere durante la cattura di variabili di contesto per un funtore passato a connect, al fine di evitare l'uso di puntatori penzolanti/riferimenti:

  1. I puntatori agli oggetti di origine e destinazione di connect può essere catturato dal valore, come sopra. È garantito che se viene richiamato il functor, esistono entrambe le estremità della connessione.

    connect(a, &A::foo, b, [a, b]{}); 
    

    scenari dove a e b sono in diversi thread richiedono particolare attenzione. Non è possibile garantire che una volta inserito il functor, alcuni thread non cancellino alcun oggetto.

    È idiomatico che un oggetto venga distrutto solo nel suo thread() o in qualsiasi thread se thread() == nullptr. Poiché il loop eventi di un thread richiama il functor, il thread nullo non è mai un problema per b - senza un thread il functor non verrà invocato. Purtroppo, non vi è alcuna garanzia sulla durata di a nella discussione di b. È quindi più sicuro acquisire invece lo stato necessario dell'azione in base al valore, in modo che la vita di a non sia un problema.

    // SAFE 
    auto aName = a->objectName();  
    connect(a, &A::foo, b, [aName, b]{ qDebug() << aName; }); 
    // UNSAFE 
    connect(a, &A::foo, b, [a,b]{ qDebug() << a->objectName(); }); 
    
  2. puntatori prime ad altri oggetti possono essere catturati per valore se si è assolutamente sicuri che il ciclo di vita degli oggetti che puntano a sovrapposizioni la durata della connessione.

    static C c; 
    auto p = &c; 
    connect(..., [p]{}); 
    
  3. Idem per i riferimenti agli oggetti:

    static D d; 
    connect(..., [&d]{}); 
    
  4. oggetti non copiabili che non derivano da QObject dovrebbe essere catturato attraverso i loro puntatori condivisi per valore.

    std::shared_ptr<E> e { new E }; 
    QSharedPointer<F> f { new F; } 
    connect(..., [e,f]{}); 
    
  5. QObject s vivono nello stesso filo può essere catturato da un QPointer; il suo valore deve essere controllato prima di essere utilizzato nel funtore.

    QPointer<QObject> g { this->parent(); } 
    connect(..., [g]{ if (g) ... }); 
    
  6. QObject s vivono in altri thread deve essere catturato da un puntatore condivisa o un puntatore debole. Il loro genitore deve essere impostata prima della loro distruzione, altrimenti dovrete doppie eliminazioni:

    class I : public QObject { 
        ... 
        ~I() { setParent(nullptr); } 
    }; 
    
    std::shared_ptr<I> i { new I }; 
    connect(..., [i]{ ... }); 
    
    std::weak_ptr<I> j { i }; 
    connect(..., [j]{ 
        auto jp = j.lock(); 
        if (jp) { ... } 
    }); 
    
+0

Questo è bello e così semplice! La risposta stava fissando proprio me. Grazie! – Addy

5

Uso lambda come slot è semplice (ad esempio per un evento da un QSpinbox):

connect(spinboxObject, &QSpinBox::editingFinished, this, [this]() {<do something>}); 

Ma funziona solo se il segnale non è sovraccarico (significa che ci sono diversi segnali con lo stesso nome ma argomenti diversi).

connect(spinboxObject, &QSpinBox::valueChange, this, [this]() {<do something>}); 

dà un errore di compilazione, perché esistono due segnali sovraccarico: ValueChanged (int) e ValueChanged (const QString &) Quindi è necessario qualificare quale versione deve essere utilizzata:

connect(spinboxObject, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int newValue){ }); 

Un po 'più breve (o meglio leggibile) è l'uso di QOverload:

connect(spinboxObject, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int newValue) { }); 
+0

Eeeek !!! che QOverload è carino! –

+0

grazie! ha aiutato :) –