Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt Slots and C++11 lambda

Tags:

c++

c++11

qt

I have a QAction item that I initialize like follows:

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

And then onSomeAction looks something like:

void MyClass::onSomeAction() {     QAction* caller = qobject_cast<QAction*>(sender());     Q_ASSERT(caller != nullptr);      // do some stuff with caller } 

This works fine, I get the caller object back and I'm able to use it as expected. Then I try the C++11 way to connect the object like such:

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

But caller is always null and thus the Q_ASSERT triggers. How can I use lambdas to get the sender?

like image 465
Addy Avatar asked Nov 01 '13 01:11

Addy


People also ask

How many signals can be connected to a slot in Qt?

Welcome to SO ! You can have as many signals you want connected to one slot and vice versa. If several slots are connected to one signal, the slots will be executed one after the other, in the order they have been connected, when the signal is emitted.

How do I connect Qt signals and slots?

To connect the signal to the slot, we use QObject::connect(). There are several ways to connect signal and slots. The first is to use function pointers: connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

Can we connect one signal with multiple slots?

One signal can be connected to many slots: When the signal is emitted, the slots are called one after the other, in an unspecified order.

What are Qt Public slots?

In C++, public means those members that are accessible from anywhere where the object is visible, private means that members are accessible only from within other members of the same class or from their friends. But in Qt, the difference in private slots and public slots seem not to exist.


1 Answers

The simple answer is: you can't. Or, rather, you don't want (or need!) to use sender(). Simply capture and use action.

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

The specification of this as the object context for the functor ensures that the functor will not get invoked if either the action or this (a QObject) cease to exist. Otherwise, the functor would try to reference dangling pointers.

In general, the following must hold when capturing context variables for a functor passed to connect, in order to avoid the use of dangling pointers/references:

  1. The pointers to the source and target objects of connect can be captured by value, as above. It is guaranteed that if the functor is invoked, both ends of the connection exist.

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

    Scenarios where a and b are in different threads require special attention. It can not be guaranteed that once the functor is entered, some thread will not delete either object.

    It is idiomatic that an object is only destructed in its thread(), or in any thread if thread() == nullptr. Since a thread's event loop invokes the functor, the null thread is never a problem for b - without a thread the functor won't be invoked. Alas, there's no guarantee about the lifetime of a in b's thread. It is thus safer to capture the necessary state of the action by value instead, so that a's lifetime is not a concern.

    // 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. Raw pointers to other objects can be captured by value if you're absolutely sure that the lifetime of the objects they point to overlaps the lifetime of the connection.

    static C c; auto p = &c; connect(..., [p]{}); 
  3. Ditto for references to objects:

    static D d; connect(..., [&d]{}); 
  4. Non-copyable objects that don't derive from QObject should be captured through their shared pointers by value.

    std::shared_ptr<E> e { new E }; QSharedPointer<F> f { new F; } connect(..., [e,f]{}); 
  5. QObjects living in the same thread can be captured by a QPointer; its value must be checked prior to use in the functor.

    QPointer<QObject> g { this->parent(); } connect(..., [g]{ if (g) ... }); 
  6. QObjects living in other threads must be captured by a shared pointer or a weak pointer. Their parent must be unset prior to their destruction, otherwise you'll have double deletes:

    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) { ... } }); 
like image 61
Kuba hasn't forgotten Monica Avatar answered Sep 29 '22 09:09

Kuba hasn't forgotten Monica