Une expression lambda évaluée est une instance du foncteur. Un foncteur est un objet avec operator()
. Les variables capturées sont des membres de cet objet foncteur. Leur durée de vie ne change pas en fonction de leur type. Ainsi, que vous capturiez des références ou des valeurs, leur durée de vie est la même. C'est votre travail de vous assurer que les références sont valides. - c'est-à-dire que les objets qu'ils référencent n'ont pas été détruits.
La durée de vie du foncteur est la même que celle de la connexion. La connexion, et donc le foncteur, durera jusqu'à ce que l'un ou l'autre :
-
QObject::disconnect()
est invoqué sur la valeur de retour de QObject::connect()
ou
-
En QObject
Sa vie se termine et son destructeur est invoqué.
Compte tenu de ce qui précède, la seule utilisation valable de la capture par référence de variables locales est celle où les variables locales survivent à la connexion. Voici quelques exemples valables :
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`
}
Ou :
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
}
Le code dans votre question est inutilement complexe. Il n'est pas nécessaire de gérer ces minuteries manuellement :
void MainWindow::onButtonClicked() {
bool foo = {};
QTimer::singleShot(1000, this, [this, foo]{ qDebug() << foo; });
}
La partie importante ici est de fournir le contexte de l'objet ( this
) comme 2ème argument de singleShot
. Cela garantit que this
doit survivre au foncteur. Inversement, le foncteur sera détruit avant que this
est détruit.
En supposant que vous vouliez vraiment instancier un nouveau timer transitoire, c'est un comportement non défini que de supprimer l'objet source du signal dans un slot connecté à un tel signal. Vous devez plutôt reporter la suppression à la boucle d'événement :
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();
});
}
Les deux sites t
y foo
sont copiés dans l'objet foncteur. L'expression lambda est un raccourci notational - vous pourriez l'écrire explicitement vous-même :
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));
}
Puisqu'une instance lambda n'est qu'un objet, vous pouvez certainement l'assigner à une variable et vous débarrasser de la duplication de code si plusieurs signaux doivent être connectés à la même lambda :
auto f = [&]{ /* code */ };
connect(o, &Class::signal1, this, f);
connect(p, &Class::signal2, this, f);
Le type du lambda est un type unique, indicible, également appelé opaque. Vous ne pouvez pas le mentionner littéralement - il n'y a aucun mécanisme dans le langage pour le faire. Vous pouvez y faire référence via decltype
seulement. Ici decltype
es el il en celui qui ne doit pas être nommé . Les C++ ont juste fait une blague sur Harry Potter, qu'ils le veuillent ou non. Je ne serai pas convaincu du contraire.