J'aimerais avoir des informations sur la façon de penser correctement les fermetures C ++ 11 et std::function
en termes de mise en œuvre et de gestion de la mémoire.
Bien que je ne crois pas à l'optimisation prématurée, j'ai l'habitude de considérer attentivement l'impact de mes choix sur les performances lors de l'écriture de nouveau code. Je fais également une bonne quantité de programmation en temps réel, par exemple sur des microcontrôleurs et pour des systèmes audio, où les pauses d'allocation / désallocation de mémoire non déterministes doivent être évitées.
Par conséquent, j'aimerais développer une meilleure compréhension du moment où utiliser ou non les lambdas C ++.
Ma compréhension actuelle est qu'un lambda sans fermeture capturée est exactement comme un rappel C. Cependant, lorsque l'environnement est capturé par valeur ou par référence, un objet anonyme est créé sur la pile. Quand une valeur-fermeture doit être retournée à partir d'une fonction, on l'enveloppe std::function
. Qu'arrive-t-il à la mémoire de fermeture dans ce cas? Est-il copié de la pile vers le tas? Est-il libéré chaque fois que le std::function
est libéré, c'est-à-dire est-il compté comme un std::shared_ptr
?
J'imagine que dans un système en temps réel, je pourrais mettre en place une chaîne de fonctions lambda, en passant B comme argument de continuation à A, afin qu'un pipeline de traitement A->B
soit créé. Dans ce cas, les fermetures A et B seraient attribuées une fois. Bien que je ne sache pas si ceux-ci seraient alloués sur la pile ou sur le tas. Cependant, en général, cela semble sûr à utiliser dans un système en temps réel. D'un autre côté, si B construit une fonction lambda C, qu'il renvoie, alors la mémoire de C serait allouée et désallouée à plusieurs reprises, ce qui ne serait pas acceptable pour une utilisation en temps réel.
En pseudo-code, une boucle DSP, qui, je pense, va être sûre en temps réel. Je veux exécuter le bloc de traitement A puis B, où A appelle son argument. Ces deux fonctions renvoient des std::function
objets, il en f
sera de même pour un std::function
objet, où son environnement est stocké sur le tas:
auto f = A(B); // A returns a function which calls B
// Memory for the function returned by A is on the heap?
// Note that A and B may maintain a state
// via mutable value-closure!
for (t=0; t<1000; t++) {
y = f(t)
}
Et un qui, je pense, pourrait être mauvais à utiliser dans le code en temps réel:
for (t=0; t<1000; t++) {
y = A(B)(t);
}
Et celui où je pense que la mémoire de pile est probablement utilisée pour la fermeture:
freq = 220;
A = 2;
for (t=0; t<1000; t++) {
y = [=](int t){ return sin(t*freq)*A; }
}
Dans ce dernier cas, la fermeture est construite à chaque itération de la boucle, mais contrairement à l'exemple précédent, elle est bon marché car elle ressemble à un appel de fonction, aucune allocation de tas n'est faite. De plus, je me demande si un compilateur pourrait "lever" la fermeture et faire des optimisations en ligne.
Est-ce correct? Je vous remercie.
operator()
. Il n'y a pas de "levage" à faire, les lambdas n'ont rien de spécial. Ils ne sont qu'un raccourci pour un objet de fonction local.