Bienvenue sur le forum !

Si vous souhaitez rejoindre la communauté, cliquez sur l'un de ces boutons !

Qt 5 : 5.7.1 - Qt Creator : 4.2.0 - Qt Installer : 2.0.3 - JOM : 1.1.2 - Qt Build suite : 1.7.0 - VS Qt 5 : 2.0.0

Nouvelle syntaxe c++11 pour signaux/slots avec fonctions lambdas

Bonjour à tous,
juste une petite astuce de syntaxe pour connecter un signal qui déclencherait un autre signal :
Ici je veux capter le signal QDialog::finished(int) et émettre un autre signal avec le même paramètre int :

connect(p_dialog, &QDialog::finished, [this](int result) {
emit dialogIsClosed(result);
});
Si @gbdivers passe par là (ou un autre membre bien sûr) , je veux bien des critiques ou d'autres manières de faire pour étoffer un peu cette courte contribution :D

Réponses

  • Ca doit fonctionner effectivement (et c'est bô ;-)).

    Mais il y a peut être plus simple car selon la doc:
    You can connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you need. It is even possible to connect a signal directly to another signal. (This will emit the second signal immediately whenever the first is emitted.)
    Donc je dirais:
    connect(p_dialog, &QDialog::finished, this, &LaClasseMonDialog::dialogIsClosed)
    est équivalent. Quand à savoir laquelle est la meilleure solution... je n'en ai aucune idée mais je pencherais intuitivement pour la seconde puisqu'elle évite une étape intermédiaire. Non ?
  • En complément ca m'est aussi arrivé d'utiliser comme tu le fais une fonction lamba comme destinatrice d'un signal et je ne m'étais pas posé la question de savoir si c'était une bonne solution. Tout ca pour dire que je suis également intéressé pas la réponse surtout si elle est critique par rapport à la manière de procéder.
  • Effectivement, ta méthode est plus rapide en syntaxe.
    L'avantage de la méthode avec lambda est que tu peux debuguer (mettre un point d'arrêt dans le corps de la fonction)
  • Oui si il y'a d'autres traitements que le simple transfert vers un autre signal alors dans tous les cas il n'y a pas le choix que de créer une fonction supplémentaire (lambda ou pas). Sinon c'est probablement superflu car il n'y a rien à déboguer ;-)
  • Dans le cas présenter ici oui,
    mais on peut effectivement mettre plein de code dans cette fonction.
  • November 2015 modifié
    Pourquoi vous persister a chercher les ennuis et demander mon avis ? Je viens, je râle, je critique, je fais des HS...

    Tant pis pour vous :)

    Pour commencer, je crois qu'il y a une recommandation quelque part dans la doc de Qt qui dit qu'il faut éviter d'appeler emit en dehors de la classe. Or une lambda est un contexte différent que cela de la classe.

    D'ailleurs, ton code ne fonctionne pas chez moi, mon compilateur me dit qu'il ne trouve pas le signal (ce qui est normal, un signal est une simple fonction membre, il faut l'appeler en spécifiant un objet).
    error: C3861: 'signalTest': identifier not found
    Le second point est l'application de la règle : "TOUJOURS tester ses pointeurs". Dans ton code en particulier, tu passes un pointeur nu, "this". Il faut donc tester ce pointeur. Comme on n'est pas dans une fonction membre, mais dans une fonction lambda, this n'est pas forcement valide.

    Habituellement, on dit que this est toujours valide dans une fonction membre, mais ici, "it's a trap!". "this" n'est pas le pointeur vers l'objet correspondant a l'appel d'une fonction membre, mais un copie de ce pointeur, qui porte le même nom, dans un autre contexte qu'une fonction membre (une fonction lambda). D'ailleurs, il est même possible de le renommer pour éviter la confusion (C++14).
    [this_in_lambda = this](){ this_in_lambda ->foo(); }
    Cf Item 31 de Effective Moderne C++.

    A cela, il faut ajouter une spécificité des connections avec lambda. Habituellement, une connexion est détruite lorsque l’émetteur ou le receveur est détruit. Avec une lambda, il n'y a pas de receveur et donc la connexion n'est pas détruite si on détruit l'objet correspondant a this.

    Je recommande d'utiliser les connexions avec lambda uniquement en interne (un objet et ses enfants). Il est également possible d'utiliser QPointer si on a un QObject, pour pouvoir checker dans la lambda si le pointeur est valide. Mais cela a un coût.

    Un petit projet de test, pour montrer le problème : https://github.com/GuillaumeBelz/Qt-Connect-Lambda

    EDIT: perte de connexion = perte d'une partie du message...
  • D'ailleurs, ton code ne fonctionne pas chez moi, mon compilateur me dit qu'il ne trouve pas le signal (ce qui est normal, un signal est une simple fonction membre, il faut l'appeler en spécifiant un objet).
    Cela fonctionne car je suppose que le connect est fait dans une fonction membre de l'objet: le this n'est pas celui capturé par la fonction lambda mais le this de la fonction où se trouve l'appel à connect. D'ailleurs si ce n'était pas le cas peut être que le compilateur accepterait un emit this->... à voir...
    Comme on n'est pas dans une fonction membre, mais dans une fonction lambda, this n'est pas forcement valide.
    Bien vu, this est valide tant que l'objet auquel il référe existe mais si il est détruit lorsque le signal est envoyé alors effectivement cela risque de faire des dégâts...
    Avec une lambda, il n'y a pas de reveceveur et donc la connection est detruite
    Il faut lire "...est non détruite..." je suppose ?
    Pourquoi vous persister a chercher les ennuis et demander mon avis ? Je viens, je râle, je critique, je fais des HS...
    Alors là aucun problème, tu viens quand tu veux, c'est toujours un plaisir de te lire.
  • perte de connexion, mon message etait incomplet, je n'avais pas vu. J'ai edite.
    Cela fonctionne car je suppose que le connect est fait dans une fonction membre de l'objet: le this n'est pas celui capturé par la fonction lambda mais le this de la fonction où se trouve l'appel à connect. D'ailleurs si ce n'était pas le cas peut être que le compilateur accepterait un emit this->... à voir...
    Si le connect est dans la fonction membre, oui le compilateur reconnaîtra le this et ça build. Mais dans mes tests, je crée la connexion en dehors (pour cela que j'avais préciser "chez moi"). Mais de toute façon, le problème est surtout cette "consigne" (règle ? recommandation ? Il faudrait que je retrouve ca) de ne pas utiliser emit en dehors des membres. (mais peut être que c'est une erreur de ma part)
  • Il faudrait que je retrouve ca) de ne pas utiliser emit en dehors des membres
    Rassures-toi tu n'es pas "gaga" c'est bien dans la doc officielle
    Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.
  • Je recommande d'utiliser les connexions avec lambda uniquement en interne (un objet et ses enfants)
    Oui c'est une excellente conclusion sur cette question, sage recommendation que je tâcherais de faire mienne.
  • Juste pour prouver si c'est nécessaire que je ne comprends rien à rien : y a-t-il des situations où il FAUT une lambda, où une fonction classique ne peut donner satisfaction ?
  • héhé, j'aime bien mon "trucs et astuces" qui finit en "c'est une mauvaise idée"

  • connect(p_dialog, &QDialog::finished, this, [this](int result) {
    emit dialogIsClosed(result);
    });
    Le code ci-dessus résoud le problème (ou une partie du problème :p).
    Remarquer le "this" supplémentaire en 3è argument, il s'agit d'un "contexte" (ce doit être un QObject),
    qui va permettre la suppression de la connection dès lors que le contexte est détruit.
    On s'assure ainsi que la fonction lambda ne sera pas appelée après que l'objet soit détruit.

    Après il reste des cas tordus où le signal (finished ici) serait émis dans le destructeur de l'objet (this ici)... ce qui a mon sens ne doit pas être très courant. Je peux essayer de retrouver la prose de Thiago à ce sujet :p

    HS: cette "petite bêtise" m'a générer qq bugs vicieux et qq heures d'arrachage de cheveux :p
  • Merci pour l'info.

    Et du coup, je comprends mieux un note que j'avais pris dans mon article et que j'avais ensuite supprimé parce que je ne la comprenais pas :
    Dans Qt 5.2, une nouvelle syntaxe a été ajoutée, pour éviter d'avoir à récupérer un objet dans la liste de capture du lambda ou récupérer un contexte dans un environnement multithreads :
    QPushButton button;
    QLabel label;
    QObject::connect(&button, &QPushButton::clicked(), label, [](){ label->setText("Mon texte"); });
    J'ai du mal noter cette syntaxe (enfin, pas cette syntaxe, la syntaxe correcte) et le pourquoi. Maintenant, c'est plus clair, je vais le remettre (et corriger) dans l'article.

    Merci
  • avec plaisir, pour une fois que c'est dans ce sens là :)

    Par contre je ne pense pas que ça dispense de la capture dans le lambda (c'est du C++ et n'a rien à voir avec la syntaxe du connect).
    C'est juste une surcharge supplémentaire pour le connect (c'est expliqué dans la doc) et il faut utiliser celle à 4 arguments par défaut je pense, cela équivaut à la vieille syntaxe: "connect(objet1, SIGNAL(...), object2, SLOT(...))" qui déconnecte quand object1 ou object2 est détruit.

    La syntaxe "connect(obj1, &signal, []() { /* lambda */ })" permet de connecter à n'importe quelle fonction (slot ou pas slot, QObject ou non, fonction membre ou non, lambda...) et est dangereuse dans le sens que tout ce qui est utilisé dans la fonction cible doit toujours exister au moment où le signal est émis.

  • Oui, c'est pour cela que je parle de "syntaxe correcte". Celle que j'avais écrite ne l'était pas et n'avait pas de sens (parce que oui, effectivement, il n'est pas possible d'accéder à label si cette variable n'est pas capturée). Et c'est bien pour cela que j'avais supprimé cette note dans l'article : je ne savais plus ce que j'avais voulu dire et cela n'avait pas de sens.
Connectez-vous ou Inscrivez-vous pour répondre.