Qt 插槽和 C++11 lambda

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19719397/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:05:25  来源:igfitidea点击:

Qt Slots and C++11 lambda

c++qtc++11

提问by Addy

I have a QAction item that I initialize like follows:

我有一个 QAction 项目,我初始化如下:

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

And then onSomeAction looks something like:

然后 onSomeAction 看起来像:

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

    // do some stuff with caller
}

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

这很好用,我取回了caller对象,并且可以按预期使用它。然后我尝试使用 C++11 方式连接对象,如下所示:

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

    // do some stuff with caller
});

But calleris always null and thus the Q_ASSERTtriggers. How can I use lambdas to get the sender?

caller始终为空,因此是Q_ASSERT触发器。如何使用 lambdas 来获取发件人?

回答by Reinstate Monica

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

简单的答案是:你不能。或者,更确切地说,您不想(或不需要!)使用sender(). 只需捕获和使用action.

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

The specification of thisas 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.

this作为函子的对象上下文的规范确保如果操作或this(a QObject) 不再存在,函子将不会被调用。否则,函子会尝试引用悬空指针。

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:

通常,在为传递给 的函子捕获上下文变量时,必须满足以下条件connect,以避免使用悬空指针/引用:

  1. The pointers to the source and target objects of connectcan 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 aand bare 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 ain 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 QObjectshould 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) { ... }
    });
    
  1. 指向源对象和目标对象的指针connect可以按值捕获,如上所述。保证如果调用函子,连接的两端都存在。

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

    场景中a,并b在不同的线程需要特别注意。不能保证一旦进入函子,某个线程不会删除任何一个对象。

    一个对象只在它的 中被破坏thread(),或者在任何线程中被破坏,这是惯用的thread() == nullptr。由于线程的事件循环调用函子,因此空线程永远不会成为问题b- 没有线程,函子将不会被调用。唉,无法保证ainb线程的生命周期。因此,通过值捕获操作的必要状态会更安全,因此a的生命周期不是问题。

    // 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. 如果您绝对确定它们指向的对象的生命周期与连接的生命周期重叠,则可以通过值捕获指向其他对象的原始指针。

    static C c;
    auto p = &c;
    connect(..., [p]{});
    
  3. 对象引用同上:

    static D d;
    connect(..., [&d]{});
    
  4. 不派生自的不可复制对象QObject应该通过它们的共享指针按值捕获。

    std::shared_ptr<E> e { new E };
    QSharedPointer<F> f { new F; }
    connect(..., [e,f]{});
    
  5. QObject同一个线程中的 s 可以被 a 捕获QPointer;在函数中使用之前必须检查它的值。

    QPointer<QObject> g { this->parent(); }
    connect(..., [g]{ if (g) ... });
    
  6. QObject存在于其他线程中的 s 必须被共享指针或弱指针捕获。他们的父级必须在销毁之前取消设置,否则您将进行双重删除:

    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) { ... }
    });
    

回答by MiB_Coder

Using lambdas as slots is straightforward (for example for an event from a QSpinbox):

使用 lambdas 作为槽很简单(例如对于来自 QSpinbox 的事件):

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

But this works only if the signal is not overloaded (that means there are several signals with the same name but different arguments).

但这只有在信号没有过载时才有效(这意味着有几个具有相同名称但参数不同的信号)。

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

gives a compile error, because there exist two overloaded signals: valueChanged(int) and valueChanged(const QString&) So it is necessary to qualify which version should be used:

给出编译错误,因为存在两个重载信号:valueChanged(int) 和 valueChanged(const QString&) 所以有必要限定应该使用哪个版本:

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

A little shorter (or better readable) is the use of QOverload:

更短(或更易读)的是使用QOverload

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

回答by Nikolay Khilyuk

Without "this" context, e.g. from main():

没有“this”上下文,例如来自 main():

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QLabel lbl{"Hello World!"};
    QPushButton btn;
    btn.show();
    lbl.show();

    QObject::connect(&btn, &QPushButton::clicked, [&lbl](){lbl.setText("Button clicked");});

    return a.exec();
}