2015-07-18 17 views
14

Rozważmy:Co się stanie, jeśli lambda zostanie przeniesiona/zniszczona podczas działania?

std::vector<std::function<void()>> vec; 
something_unmovable m; 
vec.push_back([&vec, m]() { 
    vec.resize(100); 
    // things with 'm' 
}); 
vec[0](); 

vec.resize(100) prawdopodobnie spowoduje ponowny przydział wektora, co oznacza, że ​​std::function s zostaną skopiowane do nowej lokalizacji, a stare zniszczone. A jednak dzieje się tak, gdy stara nadal działa. Ten konkretny kod działa, ponieważ lambda nic nie robi, ale wyobrażam sobie, że może to łatwo doprowadzić do niezdefiniowanego zachowania.

Co się dzieje dokładnie? Czy m jest nadal dostępny z wektora? Czy jest to, że wskaźnik lambda jest teraz niepoprawny (wskazuje na zwolnioną pamięć), więc nic z wychwytywania lambdy nie jest dostępne, ale jeśli uruchamia kod, który nie wykorzystuje niczego, co przechwytuje, nie jest to niezdefiniowane zachowanie?

Czy jest tak, że lambda jest ruchoma?

+0

Czy "nieprzenośny" oznacza "nieopisywalny" lub jest "m" kopiowalny? (W powszechnym żargonie, jeśli coś jest do skopiowania, jest ono automatycznie przenoszone, ponieważ kopia jest prawidłową implementacją ruchu.) Nie można utworzyć lambda, która przechwytuje nieodwzorowaną wartość ([demo] (https://ideone.com/ MMW5sW)). –

+1

Wziąłem "nieusuwalne", aby oznaczać tylko usunięty konstruktor ruchu z innymi domyślnymi. Możliwe jest zrobienie jednego z tych założeń. Myślę, że chodziło o pytanie, co dzieje się z zawartością przechwytywania lambda, gdy samo przechwycenie zostanie zniszczone. AFAIK, są traktowane tak samo jak konstrukcje. – defube

Odpowiedz

5

Jak już uwzględniono w innych odpowiedziach, lambda jest w istocie cukrem syntaktycznym do łatwego tworzenia typów, które zapewniają niestandardową implementację operator(). Dlatego możesz nawet pisać inwokacje lambda, używając wyraźnego odniesienia do operator(), tak jak: int main() { return [](){ return 0; }.operator()(); }. Te same zasady dla wszystkich niestatycznych funkcji składowych odnoszą się również do ciał lambda.

Te reguły zezwalają na niszczenie obiektu podczas wykonywania funkcji składowej, o ile funkcja składowa nie będzie później używać this. Twój przykład jest nietypowy, tym bardziej powszechny jest przykład dla niestatycznej funkcji członkowskiej wykonującej delete this;. This made it into the C++ FAQ, wyjaśniając, że jest to dozwolone.

Standard pozwala na to, nie reagując na to, o ile wiem. Opisuje semantykę funkcji składowych w sposób, który nie polega na tym, że obiekt nie jest niszczony, więc implementacje muszą sprawić, że funkcje członkowskie będą nadal wykonywane, nawet jeśli obiekty zostaną zniszczone.

Tak, aby odpowiedzieć na Twoje pytania:

Albo jest to, że ten wskaźnik o lambda jest nieprawidłowa (punkty uwolniony pamięci), więc nic nie fotografuje lambda mogą być dostępne, ale jeśli to działa kod to nie wykorzystuje niczego, co przechwytuje, to nie jest niezdefiniowane zachowanie?

Tak, prawie.

Czy jest tak, że lambda jest ruchoma?

Nie, nie jest.

Jedynym momentem, w którym możliwe jest przenoszenie lambdy, jest po przesunięciu lambda. W twoim przykładzie, operator() kontynuuje wykonywanie na oryginale przeniesionym z, a następnie zniszczonym funktorze.

4

Możesz traktować przechwytywania lambda jak zwykłe instancje struct.

W twoim przypadku:

struct lambda_UUID_HERE_stuff 
{ 
    std::vector<std::function<void()>> &vec; 
    something_unmovable m; 

    void operator()() 
    { 
     this->vec.resize(100); 
    } 
}; 

... i wierzę, że wszystkie te same zasady stosuje się (o ile dotyczy VS2013).

To wydaje się być kolejnym przypadkiem niezdefiniowanego zachowania. Oznacza to, że jeśli &vec ma wskazywać wektor zawierający instancję przechwytywania, a operacje w obrębie operator() powodują zmianę rozmiaru wektora.

+1

To jest całkiem bezcelowe, chyba że udowodnisz, że to UB dla struktury. –

+0

@LightnessRacesinOrbit: Powinienem był powiedzieć: "Jeśli' & vec' wskazuje wektor zawierający strukturę, to jest UB. " Aktualizowanie ... – defube

+1

Jest to w pewnym stopniu objęte [C++ FAQ - Czy to jest legalne (i moralne), aby funkcja członka powiedziała, że ​​to usunąć?] (Https://isocpp.org/wiki/faq/freestore-mgmt# delete-this), gdzie wyjaśniono, że niestatyczna funkcja członkowska może kontynuować działanie * bez * UB, nawet jeśli obiekt zostanie zniszczony, o ile funkcja członkowska nie uzyska dostępu do obiektu po jego usunięciu. – hvd

-1

Obiekty funkcyjne są zwykle kopiowalne, więc Twoja lambda będzie działać bez złego efektu. Jeśli przechwytuje przez odniesienie AFAIR, wewnętrzna implementacja użyje std :: reference_wrapper, aby lambda pozostała kopiowalna.

+2

Nie kupuję tego. Co ma z tym wspólnego obiekt funkcji, który można kopiować? –

+1

Jeśli m nie jest ruchomy, musi być możliwy do skopiowania, ponieważ przechwycenie lambda nie może go uchwycić. Dlatego lambda jest kopiowalna i nie jest ruchoma. Dlatego też realokacja wektora odbędzie się w kategoriach kopiowania, a nie przenoszenia. Dlatego lambda jest bezpieczna, ponieważ m jest kopią. –

+0

Nie, ponieważ podczas operacji kopiowania lub przenoszenia wywołanej automatyczną zmianą wektora, lambda, która jest wykonywana, jest niszczona. Właśnie o to chodzi. –

2

Ostatecznie w tym pytaniu jest wiele szczegółów, które nie są istotne. możemy zmniejszyć go prosząc o ważności:

struct A { 
    something_unmovable m; 

    void operator()() { 
     delete this; 
     // do something with m 
    } 
}; 

i poprosić o tym zachowaniu. W końcu wpływ resize() polega na wywołaniu destruktora obiektu w połowie funkcji. Niezależnie od tego, czy zostanie przeniesiony z, czy skopiowany przez std::vector, nie ma znaczenia - tak czy inaczej zostanie następnie zniszczony.

Standardowe mówi w [class.cdtor], że

dla przedmiotu z nietrywialną destructor, odnosząc się do każdego niż statyczna składowa lub zasadowej grupy przedmiotu po zakończeniu destructor wyniki wykonywania w niezdefiniowanym zachowaniu.

Więc jeśli destruktor z something_unmovable jest nietrywialne (co uczyniłoby destruktora A - albo swój lambda - nietrywialne) wszelkie odniesienia do m po destruktora nazywa zachowanie jest niezdefiniowane. Jeśli something_unmovablema mają trywialnego destruktora, to twój kod jest całkowicie dopuszczalne. Jeśli ponic nie robisz po delete this (resize() w twoim pytaniu), to jest to całkowicie poprawne zachowanie.

Czy m jest nadal dostępny z wektora?

Tak, funktor w vec[0] nadal będzie zawierał m. Może to być oryginalna lambda - lub może być kopią oryginalnej lambda. Ale będzie jeden lub drugi sposób.

Powiązane problemy