2011-12-19 8 views
8

Grałem z niektórymi nowymi funkcjami w C++ 11 i próbowałem napisać następujący program, oczekując, że nie zadziała. Ku mojemu zaskoczeniu, to robi (na GCC 4.6.1 na Linux x86 z flagą 'std = C++ 0x'):C++ Lambdas, przechwytywanie, inteligentne znaki Ptr i stos: dlaczego to działa?

#include <functional> 
#include <iostream> 
#include <memory> 

std::function<int()> count_up_in_2s(const int from) { 
    std::shared_ptr<int> from_ref(new int(from)); 
    return [from_ref]() { return *from_ref += 2; }; 
} 

int main() { 
    auto iter_1 = count_up_in_2s(5); 
    auto iter_2 = count_up_in_2s(10); 

    for (size_t i = 1; i <= 10; i++) 
     std::cout << iter_1() << '\t' << iter_2() << '\n' 
     ; 
} 

spodziewałem 'from_ref' mają zostać usunięte, gdy każda egzekucja z zwrócone przebiegi lambda. Oto moje rozumowanie: po uruchomieniu count_up_in_2s, from_ref jest wyskakiwany ze stosu, ale ponieważ zwrócona lambda nie jest od razu pobierana, ponieważ jest zwracana, nie ma innego odniesienia istniejącego przez krótki okres, dopóki ten sam odnośnik nie zostanie odłożone z powrotem, gdy lambda jest faktycznie uruchomiona, więc czy wartość referencyjna shared_ptr nie powinna wynosić zero, a następnie usunąć dane?

Chyba że przechwytywanie lambda w C++ 11 jest o wiele mądrzejsze, niż mu się przyznam, a jeśli tak, to będę zadowolony. Jeśli tak, to czy mogę założyć, że przechwytywanie zmiennych w C++ 11 pozwoli na wszystkie leksykalne oszustwa scopingu/zamykania a la Lisp tak długo, jak długo/coś/będzie zajmowało się dynamicznie przydzielaną pamięcią? Czy mogę założyć, że wszystkie przechwycone referencje pozostaną żywe, dopóki sama lambda nie zostanie usunięta, co pozwoli mi używać smart_ptrs w powyższej modzie?

Jeśli to jest tak, jak myślę, czy to nie oznacza, że ​​C++ 11 umożliwia ekspresyjne programowanie wyższego rzędu? Jeśli tak, to myślę, że komitet C++ 11 wykonał świetną robotę =)

Odpowiedz

7

Wartość lambda przechwytuje wartość from_ref, więc tworzy kopię. Z powodu tej kopii licznik ref nie jest równy 0, gdy from_ref zostanie zniszczony, jest 1 z powodu kopii, która wciąż istnieje w lambda.

+1

Więc mam rację w zrozumieniu, że sam obiekt std :: funkcja przechowuje wartości przechwytuje przez cały czas trwania instancji? A to przez przechowywanie tego odniesienia, liczba referencyjna shared_ptr nigdy nie trafi 0? O, rozumiem. Jak elegancko. – Louis

+3

@Louis nie, nie obiekt 'function', ale lambda. 'std :: function' nie wie o przechwytach lambdas. –

+0

Zatem przechwytywanie i przechowywanie odniesienia traktowane jest jako specjalny przypadek, a nie przechowywany jako członek w instancji funkcji std ::. Dzięki. – Louis

4

co następuje:

std::shared_ptr<int> from_ref(new int(from)); 
return [from_ref]() { return *from_ref += 2; }; 

jest głównie równoważny do tego:

std::shared_ptr<int> from_ref(new int(from)); 
class __uniqueLambdaType1432 { 
    std::shared_ptr<int> capture1; 
public:   
    __uniqueLambdaType1432(std::shared_ptr<int> capture1) : 
    capture1(capture1) { 
    } 
    decltype(*capture1 += 2) operator()() const { 
    return *capture1 += 2; 
    } 
}; 
return __uniqueLambdaType1432(from_ref); 

gdzie __uniqueLambdaType1432 jest typu program-globalnie unikalny w odróżnieniu od innych, w tym innych typów lambda generowanych przez lexically identyczne wyrażenie lambda. Jego rzeczywista nazwa nie jest dostępna dla programisty, a oprócz obiektu wynikającego z oryginalnego wyrażenia lambda, żadne inne jego wystąpienia nie mogą zostać utworzone, ponieważ konstruktor jest faktycznie ukryty za pomocą magii kompilatora.

1

from_ref jest przechwytywane według wartości.

Twoje rozumowanie działa jeśli zastąpić

return [from_ref]() { return *from_ref += 2; }; 

z

return [&from_ref]() { return *from_ref += 2; };