65 votes

Comment fonctionnent les variables statiques dans les objets de la fonction lambda ?

Les variables statiques utilisées dans un lambda sont-elles conservées entre les appels de la fonction dans laquelle le lambda est utilisé ? Ou bien l'objet fonction est-il "créé" à nouveau à chaque appel de fonction ?

Exemple inutile :

#include <iostream>
#include <vector>
#include <algorithm>

using std::cout;

void some_function()
{
    std::vector<int> v = {0,1,2,3,4,5};
    std::for_each( v.begin(), v.end(),
         [](const int &i)
         {
             static int calls_to_cout = 0;
             cout << "cout has been called " << calls_to_cout << " times.\n"
                  << "\tCurrent int: " << i << "\n";
             ++calls_to_cout;
         } );
}

int main()
{
    some_function();
    some_function();
}

Quel est le résultat correct de ce programme ? Est-ce que cela dépend du fait que le lambda capture ou non les variables locales ? (cela va certainement changer l'implémentation sous-jacente de l'objet fonction, donc cela pourrait avoir une influence) Est-ce une incohérence comportementale autorisée ?

Je ne cherche pas : "Mon compilateur produit ...", c'est une fonctionnalité trop nouvelle pour faire confiance aux implémentations actuelles IMHO. Je sais que demander des citations standard semble être populaire depuis que le monde a découvert qu'une telle chose existe, mais quand même, j'aimerais avoir une source décente.

3 votes

Si je comprends bien votre question, il vous manque probablement une calls_to_cout++ quelque part.

1 votes

Lambdageek : oh oui, bien vu :) thinko. Pourquoi le compilateur ne serait-il pas capable de le déduire ? Ne serait-ce pas quelque chose de cool :)

0 votes

C'est à tout le moins une très mauvaise pratique. Ainsi, indépendamment de ce que dit la norme, je m'abstiendrais d'utiliser cette (oui, je suis de l'Union européenne). Singleton considéré comme nuisible fraction).

58voto

Xeo Points 69818

Version tl;dr en bas de page.


§5.1.2 [expr.prim.lambda]

p1 expression lambda :
lambda-introducteur lambda-déclarateur opt énoncé composé

p3 Le type de expression lambda (qui est aussi le type de l'objet de fermeture) est un type de classe unique, sans nom, non syndiqué - appelé le type de fermeture - dont les propriétés sont décrites ci-dessous. Cette catégorie de classe n'est pas un agrégat (8.5.1). Le type de fermeture est déclaré dans la plus petite portée de bloc, de classe ou d'espace de nom qui contient le type de fermeture correspondant. expression lambda . ( Ma note : les fonctions ont une portée de bloc. )

p5 Le type de fermeture pour un expression lambda a un public inline fonction appel opérateur [...]

p7 Le expression lambda 's énoncé composé donne le fonction-corps (8.4) de l'opérateur d'appel de fonction [...]

Puisque l'instruction composée est directement prise comme corps de l'opérateur d'appel de fonction, et que le type de fermeture est défini dans la plus petite portée (la plus interne), cela revient à écrire ce qui suit :

void some_function()
{
    struct /*unnamed unique*/{
      inline void operator()(int const& i) const{
        static int calls_to_cout = 0;
        cout << "cout has been called " << calls_to_cout << " times.\n"
             << "\tCurrent int: " << i << "\n";
        ++calls_to_cout;

      }
    } lambda;
    std::vector<int> v = {0,1,2,3,4,5};
    std::for_each( v.begin(), v.end(), lambda);
}

Ce qui est légal en C++, les fonctions sont autorisées à avoir static les variables locales.

§3.7.1 [basic.stc.static]

p1 Toutes les variables qui n'ont pas de durée de stockage dynamique, qui n'ont pas de durée de stockage par fil et qui ne sont pas locales ont une durée de stockage statique. Le stockage de ces entités durera toute la durée du programme. .

p3 Le mot-clé static peut être utilisé pour déclarer une variable locale avec une durée de stockage statique. [...]

§6.7 [stmt.dcl] p4
(Ceci traite de l'initialisation des variables avec une durée de stockage statique dans une portée de bloc).

[...] Sinon, une telle variable est initialisée la première fois que le contrôle passe par sa déclaration ; [...]


Je le répète :

  • Le type d'une expression lambda est créé dans la portée la plus interne.
  • Il est no créé à nouveau pour chaque appel de fonction (cela n'aurait pas de sens, puisque le corps de la fonction englobante serait comme mon exemple ci-dessus).
  • Il obéit à (presque) toutes les règles des classes et des structures normales (juste quelques trucs à propos de this est différent), puisqu'il es une catégorie de classe non syndiquée.

Maintenant que nous nous sommes assurés que pour chaque appel de fonction, le type de fermeture est le même, nous pouvons dire sans risque que la variable locale statique est également la même ; elle est initialisée la première fois que l'opérateur d'appel de fonction est invoqué et vit jusqu'à la fin du programme.

3 votes

Cela ne répond pas à la question de savoir si la classe locale est différente à chaque appel de fonction ou pas, c'est-à-dire si la variable statique est réinitialisée à chaque invocation de la fonction some_function ... ?

0 votes

@Kerrek : Attendez, il me manque une citation. Donne-moi une seconde. - Fait

1 votes

OK, mais je ne suis toujours pas certain que la classe locale lambda est toujours le même ou s'il s'agit d'une classe différente à chaque fois. Pour l'expression lambda proprement dite, le type de fermeture est "unique" pour chaque évaluation de l'expression, ce qui est donc assez important.

19voto

bames53 Points 38303

La variable statique doit se comporter comme elle le ferait dans le corps d'une fonction. Cependant, il y a peu de raisons d'en utiliser une, puisqu'un objet lambda peut avoir des variables membres.

Dans la suite, calls_to_cout est capturée par value, qui donne à la lambda une variable membre du même nom, initialisée à la valeur actuelle de calls_to_cout . Cette variable membre conserve sa valeur à travers les appels mais est locale à l'objet lambda, donc toutes les copies du lambda auront leur propre variable membre calls_to_cout au lieu de partager une variable statique. C'est beaucoup plus sûr et plus efficace.

(et puisque les lambdas sont const par défaut et que ce lambda modifie calls_to_cout il doit être déclaré comme mutable).

void some_function()
{
    vector<int> v = {0,1,2,3,4,5};
    int calls_to_cout = 0;
    for_each(v.begin(), v.end(),[calls_to_cout](const int &i) mutable
    {
        cout << "cout has been called " << calls_to_cout << " times.\n"
          << "\tCurrent int: " << i << "\n";
        ++calls_to_cout;
    });
}

Si vous faire Si vous voulez qu'une seule variable soit partagée entre les instances de la lambda, il est encore préférable d'utiliser des captures. Il suffit de capturer une sorte de référence à la variable. Par exemple, voici une fonction qui renvoie une paire de fonctions qui partagent une référence à une variable unique, et chaque fonction effectue sa propre opération sur cette variable partagée lorsqu'elle est appelée.

std::tuple<std::function<int()>,std::function<void()>>
make_incr_reset_pair() {
    std::shared_ptr<int> i = std::make_shared<int>(0);
    return std::make_tuple(
      [=]() { return ++*i; },
      [=]() { *i = 0; });
}

int main() {
    std::function<int()> increment;
    std::function<void()> reset;
    std::tie(increment,reset) = make_incr_reset_pair();

    std::cout << increment() << '\n';
    std::cout << increment() << '\n';
    std::cout << increment() << '\n';
    reset();
    std::cout << increment() << '\n';

9voto

QuentinUK Points 995

Un statique peut être construit dans la capture:-

auto v = vector<int>(99);
generate(v.begin(), v.end(), [x = int(1)] () mutable { return x++; });

Le lambda peut être fait par un autre lambda

auto inc = [y=int(1)] () mutable { 
    ++y; // has to be separate, it doesn't like ++y inside the []
    return [y, x = int(1)] () mutable { return y+x++; }; 
};
generate(v.begin(), v.end(), inc());

Ici, y peut également être capturé par référence tant que inc dure plus longtemps.

4voto

Fabio Points 649

Il existe deux façons d'utiliser les états avec les lambdas.

  1. En définissant la variable comme static dans le lambda : la variable est persistante à travers les appels et instanciations de lambda.
  2. Définir la variable dans la capture lambda et marquer la lambda en tant que mutable la variable est persistante sur les appels lambda mais elle est réinitialisée à chaque instanciation lambda.

Le code suivant illustre la différence :

void foo() {
   auto f = [k=int(1)]() mutable { cout << k++ << "\n";}; // define k in the capture
   f();
   f();
}

void bar() {
   auto f = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
   f();
   f();
}

void test() {
   foo();
   foo();  // k is reset every time the lambda is created
   bar();
   bar();  // k is persistent through lambda instantiations
   return 0;
}

2voto

David Seiler Points 6212

Je n'ai pas de copie de la norme finale, et la projet ne semble pas aborder la question de manière explicite (voir la section 5.1.2, qui commence à la page 87 du PDF). Mais il est dit qu'une expression lambda est évaluée à un seul objet de type type de fermeture qui peut être invoquée à plusieurs reprises. Ceci étant, je crois que la norme exige que les variables statiques soient initialisées une fois et une seule, comme si vous aviez écrit la classe, operator() et la capture variable à la main.

Mais comme vous le dites, il s'agit d'une nouvelle fonctionnalité ; au moins pour l'instant, vous êtes coincé avec ce que votre implémentation fait, peu importe ce que dit la norme. Il est de toute façon préférable de capturer explicitement une variable dans la portée englobante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X