An evaluated lambda expression is a functor instance. A functor is an object with operator()
. The captured variables are members of that functor object. Their lifetime doesn't change based on their type. Thus, whether you capture references or values, their lifetime is the same. It's your job to ensure that the references are valid - i.e. that the objects they reference haven't been destroyed.
The functor's lifetime is the same as the connection's lifetime. The connection, and thus the functor, will last until either:
QObject::disconnect()
is invoked on the return value of QObject::connect()
, or
The QObject
's life ends and its destructor is invoked.
Taking the above into account, the only valid use of local variable capture-by-reference is when the local variables outlive the connection. Some valid examples would be:
void test1() {
int a = 5;
QObject b;
QObject:connect(&b, &QObject::destroyed, [&a]{ qDebug() << a; });
// a outlives the connection - per C++ semantics `b` is destroyed before `a`
}
Or:
int main(int argc, char **argv) {
QObject * focusObject = {};
QApplication app(argc, argv);
QObject * connect(&app, &QGuiApplication::focusObjectChanged,
[&](QObject * obj){ focusObject = obj; });
//...
return app.exec(); // focusObject outlives the connection too
}
The code in your question is needlessly complex. There's no need to manage such timers manually:
void MainWindow::onButtonClicked() {
bool foo = {};
QTimer::singleShot(1000, this, [this, foo]{ qDebug() << foo; });
}
The important part here is to provide the object context (this
) as the 2nd argument of singleShot
. This ensures that this
must outlive the functor. Conversely, the functor will be destroyed before this
is destroyed.
Assuming that you really wanted to instantiate a new transient timer, it is undefined behavior to delete the signal's source object in a slot connected to such a signal. You must defer the deletion to the event loop instead:
void MainWindow::onButtonClicked()
{
auto t = new QTimer(this);
bool foo = {};
t->setSingleShot(true);
t->setInterval(1000);
t->start();
connect(t, &QTimer::timeout, [=](){
qDebug() << foo;
t->deleteLater();
});
}
Both t
and foo
are copied into the functor object. The lambda expression is a notational shorthand - you could write it explicitly yourself:
class $OpaqueType {
QTimer * const t;
bool const foo;
public:
$OpaqueType(QTimer * t, bool foo) :
t(t), foo(foo) {}
void operator()() {
qDebug() << foo;
t->deleteLater();
}
};
void MainWindow::onButtonClicked() {
//...
connect(t, &QTimer::timeout, $OpaqueType(t, foo));
}
Since a lambda instance is just an object, you can certainly assign it to a variable and get rid of code duplication should more than one signal need to connect to same lambda:
auto f = [&]{ /* code */ };
connect(o, &Class::signal1, this, f);
connect(p, &Class::signal2, this, f);
The type of the lambda is a unique, unutterable, also called opaque, type. You cannot mention it literally - there's no mechanism in the language to do so. You can refer to it via decltype
only. Here decltype
is the he in he who shall not be named. C++ people just worked in a Harry Potter joke, whether they meant to or not. I won't be convinced otherwise.