Pytanie Czy argument konstruktora lambda C ++ może przechwycić konstruowaną zmienną?


Następujące kompiluje. Ale czy istnieje kiedykolwiek jakiś zwisający temat odniesienia?

    class Foo {
         Foo(std::function<void(int)> fn) { /* etc */ }
    }

    void f(int i, Foo& foo) { /* stuff with i and foo */ }

    Foo foo([&foo](int i){f(i, foo);});

Wydaje się działać. (Prawdziwa lambda jest oczywiście bardziej skomplikowana.)


11
2018-04-20 02:59


pochodzenie


Prawdopodobnie warto zauważyć, że jeśli użyjesz lambda w konstruktorze, mogą się zdarzyć złe rzeczy. - Michael Anderson
jeśli przechwycisz go kopią, otrzymasz ostrzeżenie z klangiem variable is uninitialized when used within its own initialization - Drax


Odpowiedzi:


Ale czy istnieje kiedykolwiek jakiś zwisający temat odniesienia?

To zależy całkowicie od tego, co robisz Foo. Oto przykład, który by mają wiszące problemy referencyjne:

struct Foo {
     Foo() = default;
     Foo(std::function<void(int)> fn) : fn(fn) { }
     std::function<void(int)> fn;
}

Foo outer;
{
    Foo inner([&inner](int i){f(i, inner);});
    outer = inner;
}
outer.fn(42); // still has reference to inner, which has now been destroyed

5
2018-04-20 03:08



Więc rozsądnie byłoby powiedzieć, że mój wzorzec nie jest bardziej niebezpieczny niż zwisające odniesienia w ogóle? Żywot mojego życia foo nie stanowi problemu w tym przypadku. Zauważyłem, że konstrukcja jest dość dziwna, przechwytując odniesienie do obiektu, który wciąż się buduje. - Andrew Lazarus
@AndrewLazarus Tak, w zasadzie. Po prostu dziwne. - Barry


Wyrażenie lambda [&foo](int i){f(i, foo);} poprowadzi kompilator do wygenerowania klasy zamknięcia podobnej do tej (ale nie do końca poprawnej):

class _lambda
{
    Foo& mFoo; // foo is captured by reference

public:
    _lambda(Foo& foo) : mFoo(foo) {}

    void operator()(int i) const
    {
       f(i, mFoo);
    }
};

Dlatego deklaracja Foo foo([&foo](int i){f(i, foo);}); jest traktowany jako Foo foo(_lambda(foo));. Przechwytywanie foo samo w sobie podczas tworzenia nie ma problemu w tej sytuacji, ponieważ wymagany jest tylko jego adres (referencje są zwykle realizowane za pomocą wskaźników).

Typ std::function<void(int)> wewnętrznie skopli skonstruować ten typ lambda, co oznacza argument konstruktora Foo fn trzyma kopię _lambda obiekt (który zawiera odniesienie (np. mFoo) do twojego foo).

Oznacza to, że zwisający problem referencyjny może pojawić się w niektórych sytuacjach, na przykład:

std::vector<std::function<void(int)>> vfn; // assume vfn live longer than foo

class Foo {
     Foo(std::function<void(int)> fn) { vfn.push_back(fn); }
}

void f(int i, Foo& foo) { /* stuff with i and foo */ }

Foo foo([&foo](int i){f(i, foo);});

....

void ff()
{
    // assume foo is destroyed already,
    vfn.pop_back()(0); // then this passes a dangling reference to f.
}

4
2018-04-20 12:07