Question Quelle est la durée de vie de la cible du pointeur vers la fonction pointant vers un lambda?


Désolé, c'est une question interminable, mais permettez-moi de la décomposer:

Le standard C ++ garantit-il que:

void (*Ptr)(void) = [] {};
return Ptr;

sera toujours défini comportement?

Je comprends que, pour une fermeture, il sera défini, car cet objet de fermeture est déplacé / copié par valeur; mais, même si je sais qu'une fonction «normale» a une durée de vie infinie / nulle, la cible de Ptr a-t-elle la même chose? Ou est-il détruit et recréé à chaque instanciation de la lambda?

La raison pour laquelle je m'intéresse, c'est que je ne peux pas utiliser les lambda comme callbacks, sinon. Je veux savoir.


11
2018-05-08 02:30


origine


Réponses:


Les objets ont des durées de vie; les fonctions ne pas. Les fonctions ne vivent pas ou ne meurent pas; ils existent toujours. En tant que telle, une fonction ne peut pas "sortir de la portée", ni la fonction pointée par un pointeur de fonction précédemment valide disparaître. Indépendamment de leur provenance, les pointeurs de fonction sont toujours valides.

Maintenant, cela ignore le chargement dynamique et ainsi de suite, mais c'est un comportement extra-standard.

Le pointeur de fonction que vous recevez d'un lambda est un pointeur de fonction. Ce n'est pas spécial ou magique. Il ne se comporte donc pas différemment de tout autre pointeur de fonction.


Est-il possible que le résultat de la conversion en void (*) () pointe vers quelque chose qui appelle une fonction membre liée à un objet?

C'est une question beaucoup plus complexe. Celui dont la norme semble plutôt sous-spécifiée. La norme dit seulement:

l'adresse d'une fonction qui, lorsqu'elle est invoquée, a le même effet que l'appel de l'opérateur d'appel de fonction du type de fermeture.

Ce que "le même effet" signifie exactement est la question. On pourrait soutenir que "le même effet" signifie faire ce que l'opérateur d'appel de fonction aurait fait, en exécutant la même séquence d'instructions. On pourrait également faire valoir que "le même effet" signifierait invoquer l'objet de fermeture lui-même.

Ce dernier cas peut sembler difficile à implémenter, mais rappelez-vous que la magie du compilateur peut être utilisée. La fermeture pourrait renvoyer un pointeur de fonction spécifique à l'instance, alloué par la fermeture sur demande. Ou quelque chose comme ça.

Le texte non normatif ne semble pas très clair sur ce point. Il y a un exemple de fermeture pour un lambda (générique), qui dit ceci:

template<class T> auto operator()(T t) const { ... }
template<class T> static auto lambda_call_operator_invoker(T a) {
// forwards execution to operator()(a) and therefore has
// the same return type deduced
...
}

Le commentaire en question suggère un transfert, mais cela nécessiterait que l'appel statique construise une nouvelle instance de lambda avec laquelle effectuer le transfert.

Dans l'ensemble, la norme n'indique pas clairement si l'appel du pointeur de fonction généré après la destruction de la fermeture est légal.


5
2018-05-08 03:36



une fonction lambda est juste un sucre syntaxique pour une fonction réelle ou un foncteur (c'est-à-dire un objet avec operator() et certains membres, définis généralement au moment de la construction). Ainsi, une telle fonction ou méthode est définie statiquement lors de la compilation.

Bien que la norme ne spécifie peut-être pas exactement la méthode d'implémentation, comme l'a fait remarquer @NicolBolas, il semble que les implémentations pratiques suivent la ligne directrice stricte: un lambda sans contexte peut être converti en pointeur, aucun objet intermédiaire n'est créé. le lieu de la défaite lambda, ni dans le lieu de l'invocation. Je viens de le vérifier (encore une fois) pour gcc et clang et je suis presque sûr que MSVC fait de même.

Remarque: Le reste concerne les lambdas avec des contextes, et bien que cela me semble plus intéressant et pratique, la question traite explicitement des lambda sans contexte.

Un contexte est stocké dans un lambda (pensez à un objet foncteur avec des arguments significatifs, reçus lors de la construction de l'objet). Donc, si vous transmettez des références ou des pointeurs au contexte, ces références et pointeurs (par ex. this) ne prolongera pas automatiquement la durée de vie de leurs entités correspondantes. C'est pourquoi vous devez faire très attention lorsque vous enregistrez un lambda dans une portée autre que celle définie dans.

Voir un exemple de problèmes liés à lambda et sa définition de portée de contexte dans ce problème résolu. Vérifiez le correctif pour voir ce qui a été fait pour rendre les lambdas stockés avec des contextes sûrs.


2
2018-05-08 02:53