2016-07-13 11 views
24

Mam funkcji w klasie, która definiuje lambda i przechowuje je w zmiennej lokalnej statycznej:Kiedy jest "to" zrobione w lambda?

class A 
{ 
public: 
    void call_print() 
    { 
     static auto const print_func = [this] { 
      print(); 
     }; 

     print_func(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

ja również wykonać następujący test:

int main() 
{ 
    A a; 
    B b; 

    a.call_print(); 
    b.call_print(); 
} 

(Live Sample)

To, czego się spodziewam wydrukować, to:

A::print() 
B::print() 

Ale to, co naprawdę jest:

A::print() 
A::print() 

(adres ten sam obiekt jest również drukowane z każdym)

Podejrzewam, to ze względu na wychwytywanie this. Zakładałem, że przechwyci on wartość this, gdy zostanie wywołana, ale wydaje się, że jest przechwytywana w momencie zdefiniowania lambda.

Czy ktoś mógłby wyjaśnić semantyki przechwytów lambda? Kiedy faktycznie dostają się do funkcji? Czy jest to takie samo dla wszystkich typów przechwytywania, czy też jest to specjalny przypadek? Usunięcie static rozwiązuje problem, jednak w moim kodzie produkcyjnym faktycznie przechowuję lambdę w nieco cięższym obiekcie, który reprezentuje gniazdo, do którego wstawiam sygnał później.

Odpowiedz

36

Nie ma to nic wspólnego z semantyką wychwytywania lambda. Po prostu działa static.

Zmienna o zasięgu funkcji jest zainicjowana dokładnie raz. W całym twoim programie jest tylko jeden taki obiekt. Zostanie zainicjalizowany za pierwszym razem, gdy funkcja zostanie wywołana (dokładniej, po pierwszym wykonaniu instrukcji static). Dlatego też wyrażenie użyte do zainicjowania zmiennej static jest tylko raz wywoływane.

Jeśli zmienna o zakresie funkcji static jest zainicjowana danymi opartymi na jednym z parametrów tej funkcji (np. this), to otrzyma parametry tylko od pierwszego wywołania tej funkcji.

Twój kod tworzy pojedynczą wartość lambda. Nie tworzy różnych lambd dla każdego wywołania funkcji.

Zachowanie, które wydaje się być pożądane, nie jest lokalną zmienną static, lecz elementem obiektu. Więc po prostu umieść obiekt std::function w samej klasie, a następnie call_print zainicjuj go, jeśli jest pusty.

+11

Może warto dodać, że to rzeczywiście stanowi bardzo niebezpieczny styl programowania, ponieważ jeśli 'a' zostaje usunięty, przyszłe wywołania' call_print' wywołają niezdefiniowane zachowanie i bardzo prawdopodobne awarie. – Xirema

+0

@Xirema To prawda, ale nie sądzę, że jest to zachowanie, które próbuje uzyskać. –

+0

Rozumiem semantykę słowa "statyczny", próbuję zrozumieć, czy lambda "obiecuje" uchwycić "to" * później *, lub jeśli zostanie przechwycone * w prawo *. W swojej odpowiedzi zareagowałeś na to. Chciałem, aby kontener dla lambda był statyczny, ale nie przechwytuje. Miałem nadzieję, że zostały rozdzielone między definicją i inwokacją. –

2

Rzeczywiście, wartość przechwytywania jest ustawiana, gdy lambda jest zdefiniowana, a nie kiedy jest wywoływana. Ponieważ ustawiasz zmienną statyczną na wyrażenie, które definiuje lambdę, dzieje się tak tylko za pierwszym razem, gdy wywoływana jest funkcja call_print (według reguł rządzących zmiennymi statycznymi). Tak więc wszystkie wywołania call_print wywołują w rzeczywistości tę samą wartość lambda, czyli tę, której this ustawiono na &a.

12

Funkcja lambda jest tworzona przy pierwszym wywołaniu funkcji zamykającej A::call_print(). Od pierwszego wywołania tego obiektu na obiekcie A jest przechwytywany obiekt this tego obiektu.Jeśli odwrócić kolejność wywołania, można zobaczyć inny wynik:

b.call_print(); 
a.call_print(); 

wyjściowa:

B::print() 
B::print() 

on więcej wspólnego z semantyki inicjalizacji funkcji lokalnego statycznych obiektów niż te z przechwytywanie lambda.

8

Statyczne zmienne lokalne są inicjalizowane przy pierwszym wykonaniu ich deklaracji.

W tym przypadku:

  1. Tworzenie lambda, przechwytywanie & A jak ten, który wywołuje A.print();
  2. przypisać lambda print_func
  3. połączenia, które lambda (poprzez print_func)
  4. połączenia, które lambda ponownie.

Przechwycone wartości są zawsze przechwytywane po utworzeniu lambda - co w tym przypadku następuje podczas pierwszego połączenia z call_print.

4

Nie sądzę, że przechwytywanie jest tutaj problemem, ale słowo kluczowe static. Pomyśl o kodzie jak poniżej:

class A 
{ 
public: 
    void call_print() 
    { 
     static A* ptr = this; 

     ptr->print(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

To jest prawie tak samo bez lambda.

Jeśli spojrzysz na kod, stanie się całkiem jasne, że twój telefon a.call_print(); inicjuje ptr za pomocą wskaźnika do obiektu a, który jest następnie używany dalej.

3

To pytanie dotyczy nie tylko zachowania lambdas, ale także zachowania zmiennych statycznych w funkcjach.

Zmienna jest tworzona przy pierwszym przepłynięciu przez nią kodu, przy użyciu dowolnych zmiennych dostępnych w tym czasie.

przykład:

#include <iostream> 

int foo(int v) 
{ 
    static int value = v; 
    return v; 
}; 

int main() 
{ 
    std::cout << foo(10) << std::endl; 
    std::cout << foo(11) << std::endl; 
} 

spodziewanych:

10 
10 

Bo to odpowiednik:

foo::value = 10; 
std::cout << foo::value << std::endl; 
// foo::value = 11; (does not happen) 
std::cout << foo::value << std::endl; 
Powiązane problemy