75 votes

Quelle est la durée de vie d'une expression lambda C++ ?

(J'ai lu Quelle est la durée de vie des foncteurs implicites dérivés de lambda en C++ ? déjà et il ne répond pas à cette question).

Je comprends que la syntaxe lambda C++ n'est que du sucre pour créer une instance d'une classe anonyme avec un opérateur d'appel et un certain état, et je comprends les exigences de durée de vie de cet état (décidées selon que vous capturez par valeur ou par référence). Dans l'exemple suivant, l'objet std::function instance retournée va être utile ?

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

Si c'est le cas, comment cela fonctionne-t-il ? ? Cela me semble un peu trop magique - je ne peux imaginer que cela fonctionne en std::function copier toute mon instance, ce qui peut être très lourd selon ce que j'ai capturé - dans le passé j'ai utilisé std::function principalement avec des pointeurs de fonctions nus, et les copier est rapide. Cela semble également problématique à la lumière de std::function de l'effacement de type.

67voto

Dennis Zickefoose Points 6659

La durée de vie est exactement ce qu'elle serait si vous remplaciez votre lambda par un foncteur à commande manuelle :

struct lambda {
   lambda(int x) : x(x) { }
   int operator ()(int y) { return x + y; }

private:
   int x;
};

std::function<int(int)> meta_add(int x) {
   lambda add(x);
   return add;
}

L'objet sera créé, local à la meta_add puis s'est déplacé [dans sa totalité, y compris la valeur de x ] dans la valeur de retour, alors l'instance locale sortira du périmètre et sera détruite normalement. Mais l'objet retourné par la fonction restera valide aussi longtemps que la fonction std::function l'objet qui le détient le fait. La durée de cette période dépend évidemment du contexte d'appel.

18voto

deft_code Points 19418

Il semble que vous soyez plus confus au sujet std::function que les lambdas.

std::function utilise une technique appelée effacement de type. Voici un survol rapide.

class Base
{
  virtual ~Base() {}
  virtual int call( float ) =0;
};

template< typename T>
class Eraser : public Base
{
public:
   Eraser( T t ) : m_t(t) { }
   virtual int call( float f ) override { return m_t(f); }
private:
   T m_t;
};

class Erased
{
public:
   template<typename T>
   Erased( T t ) : m_erased( new Eraser<T>(t) ) { }

   int do_call( float f )
   {
      return m_erased->call( f );
   }
private:
   Base* m_erased;
};

Pourquoi voudriez-vous effacer les caractères ? Le type que nous voulons n'est-il pas juste int (*)(float) ?

Ce que l'effacement de type permet est Erased peut maintenant stocker n'importe quelle valeur qui peut être appelée, par exemple int(float) .

int boring( float f);
short interesting( double d );
struct Powerful
{
   int operator() ( float );
};

Erased e_boring( &boring );
Erased e_interesting( &interesting );
Erased e_powerful( Powerful() );
Erased e_useful( []( float f ) { return 42; } );

12voto

Loki Astari Points 116129

C'est ça :

[x](int y) { return x + y; };

Est équivalent à : (ou peut être considéré comme tel)

struct MyLambda
{
    MyLambda(int x): x(x) {}
    int operator()(int y) const { return x + y; }
private:
    int x;
};

Donc votre objet renvoie un objet qui ressemble à ça. Qui a un constructeur de copie bien défini. Donc il semble très raisonnable qu'il puisse être correctement copié hors d'une fonction.

4voto

Daniel Trebbien Points 18089

Dans le code que vous avez posté :

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

Le site std::function<int(int)> qui est retourné par la fonction contient en fait une instance déplacée de l'objet de la fonction lambda qui a été assigné à la variable locale add .

Lorsque vous définissez un lambda C++11 qui saisit par valeur ou par référence, le compilateur C++ génère automatiquement un type fonctionnel unique, dont une instance est construite lorsque le lambda est appelé ou affecté à une variable. Pour illustrer, votre compilateur C++ pourrait générer le type de classe suivant pour le lambda défini par [x](int y) { return x + y; } :

class __lambda_373s27a
{
    int x;

public:
    __lambda_373s27a(int x_)
        : x(x_)
    {
    }

    int operator()(int y) const {
        return x + y;
    }
};

Ensuite, le meta_add est essentiellement équivalent à :

std::function<int(int)> meta_add(int x) {
    __lambda_373s27a add = __lambda_373s27a(x);
    return add;
}

EDIT : Au fait, je ne sais pas si vous le savez, mais voici un exemple de fonction currying en C++11.

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