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
Qt Slots and C++11 lambda
提问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 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:
这很好用,我取回了caller
对象,并且可以按预期使用它。然后我尝试使用 C++11 方式连接对象,如下所示:
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?
但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 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.
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
,以避免使用悬空指针/引用:
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
andb
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 ifthread() == nullptr
. Since a thread's event loop invokes the functor, the null thread is never a problem forb
- without a thread the functor won't be invoked. Alas, there's no guarantee about the lifetime ofa
inb
's thread. It is thus safer to capture the necessary state of the action by value instead, so thata
'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(); });
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]{});
Ditto for references to objects:
static D d; connect(..., [&d]{});
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]{});
QObject
s living in the same thread can be captured by aQPointer
; its value must be checked prior to use in the functor.QPointer<QObject> g { this->parent(); } connect(..., [g]{ if (g) ... });
QObject
s 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) { ... } });
指向源对象和目标对象的指针
connect
可以按值捕获,如上所述。保证如果调用函子,连接的两端都存在。connect(a, &A::foo, b, [a, b]{});
场景中
a
,并b
在不同的线程需要特别注意。不能保证一旦进入函子,某个线程不会删除任何一个对象。一个对象只在它的 中被破坏
thread()
,或者在任何线程中被破坏,这是惯用的thread() == nullptr
。由于线程的事件循环调用函子,因此空线程永远不会成为问题b
- 没有线程,函子将不会被调用。唉,无法保证a
inb
线程的生命周期。因此,通过值捕获操作的必要状态会更安全,因此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(); });
如果您绝对确定它们指向的对象的生命周期与连接的生命周期重叠,则可以通过值捕获指向其他对象的原始指针。
static C c; auto p = &c; connect(..., [p]{});
对象引用同上:
static D d; connect(..., [&d]{});
不派生自的不可复制对象
QObject
应该通过它们的共享指针按值捕获。std::shared_ptr<E> e { new E }; QSharedPointer<F> f { new F; } connect(..., [e,f]{});
QObject
同一个线程中的 s 可以被 a 捕获QPointer
;在函数中使用之前必须检查它的值。QPointer<QObject> g { this->parent(); } connect(..., [g]{ if (g) ... });
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();
}