40 votes

C++11 lambdas : capture de variable membre gotcha

Considérez ce code :

#include <memory>
#include <iostream>

class A
{
public:
    A(int data) : data_(data)
    { std::cout << "A(" << data_ << ")" << std::endl; }
    ~A() { std::cout << "~A()" << std::endl; }
    void a() { std::cout << data_ << std::endl; }
private:
    int data_;
};

class B
{
public:
    B(): a_(new A(13)) { std::cout << "B()" << std::endl; }
    ~B() { std::cout << "~B()" << std::endl; }
    std::function<void()> getf()
    {
        return [=]() { a_->a(); };
    }
private:
    std::shared_ptr<A> a_;
};

int main()
{
    std::function<void()> f;
    {
        B b;
        f = b.getf();
    }
    f();
    return 0;
}

Ici, on dirait que je capture a_ par valeur, mais lorsque je l'exécute sous Linux (GCC 4.6.1), ceci s'affiche :

A(13)
B()
~B()
~A()
0

Évidemment, 0 est faux, car A est déjà détruit. Cela ressemble à this est en fait capturée et est utilisée pour rechercher this->a_ . Mes soupçons sont confirmés lorsque je change la liste de capture de [=] a [=,a_] . La sortie correcte est alors imprimée et la durée de vie des objets est conforme aux attentes :

A(13)
B()
~B()
13
~A()

La question :

Ce comportement est-il spécifié par la norme, défini par l'implémentation ou non défini ? Ou bien je suis fou et il s'agit de quelque chose d'entièrement différent ?

40voto

Nicol Bolas Points 133791

Ce comportement est-il spécifié par la norme

Oui. La capture de variables membres se fait toujours via la capture de this ; c'est la seule façon d'accéder à une variable membre. Dans la portée d'une fonction membre a_ est équivalent à (*this).a_ . C'est également vrai pour les lambdas.

Par conséquent, si vous utilisez this (implicitement ou explicitement), alors vous devez vous assurer que l'objet reste en vie tant que l'instance lambda est présente.

Si vous voulez le capturer par valeur, vous devez le faire explicitement :

std::function<void()> getf()
{
    auto varA = a_;
    return [=]() { varA->a(); };
}

Si vous avez besoin d'un devis spécifique :

L'énoncé composé de l'expression lambda produit le corps de fonction ( 8.4 ) de l'opérateur d'appel de fonction, mais pour les besoins de la recherche de nom (3.4), de la détermination du type et de la valeur de this (9.3.2) et de la transformation des expressions d'identification se référant à des membres de classe non statiques en expressions d'accès aux membres de classe en utilisant (*this) ( 9.3.1 ), l'énoncé composé est considéré dans le contexte de l'expression lambda.

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