web-dev-qa-db-fra.com

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++ est juste du sucre pour créer une instance d'une classe anonyme avec un opérateur d'appel et un état, et je comprends les exigences de durée de vie de cet état (décidé selon que vous capturez par valeur de par référence.) Mais qu'est-ce que la durée de vie de l'objet lambda lui-même? Dans l'exemple suivant, l'instance std::function Renvoyée sera-t-elle 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 ça marche ? Cela me semble un peu trop magique - je peux seulement imaginer que cela fonctionne en std::function En copiant toute mon instance, ce qui pourrait être très lourd en fonction de ce que j'ai capturé - dans le passé, j'ai utilisé std::function principalement avec des pointeurs de fonction nus, et leur copie est rapide. Cela semble également problématique à la lumière de l'effacement de type de std::function.

65
user79758

La durée de vie est exactement ce qu'elle serait si vous remplaçiez votre lambda par un foncteur roulé à la main:

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éé, localement à meta_add, puis déplacé [dans son intégralité, y compris la valeur de x] dans la valeur de retour, puis l'instance locale sortira du champ d'application et sera détruite normalement. Mais l'objet renvoyé par la fonction restera valide aussi longtemps que std::function l'objet qui le détient fait. La durée dépend évidemment du contexte d'appel.

59
Dennis Zickefoose

Il semble que vous soyez plus confus à propos de std::function Que de 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 le type? N'est-ce pas le type que nous voulons juste int (*)(float)?

Ce que l'effacement de type permet est que Erased peut maintenant stocker n'importe quelle valeur qui peut être appelée comme 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; } );
16
deft_code

C'est:

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

Est équivalent à: (Ou peut être considéré aussi)

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

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

11
Martin York

Dans le code que vous avez publié:

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

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

Lorsque vous définissez un lambda C++ 11 qui capture 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++ peut 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, la fonction meta_add Est essentiellement équivalente à:

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

EDIT: Soit dit en passant, je ne sais pas si vous le savez, mais ceci est un exemple de fonction currying en C++ 11.

4
Daniel Trebbien