Omawiana lambda w rzeczywistości nie ma stanu .
Zbadać:
struct lambda {
auto operator()() const { return 17; }
};
A gdybyśmy to zrobili lambda f;
, to jest pusta klasa. Powyższe jest nie tylko lambda
funkcjonalnie podobne do Twojej lambdy, ale (w zasadzie) jest to sposób implementacji Twojej lambdy! (Wymaga również niejawnego rzutowania na operatora wskaźnika funkcji, a nazwa lambda
zostanie zastąpiona jakimś pseudo-guidem wygenerowanym przez kompilator)
W C ++ obiekty nie są wskaźnikami. To są rzeczywiste rzeczy. Zajmują tylko przestrzeń wymaganą do przechowywania w nich danych. Wskaźnik do obiektu może być większy niż obiekt.
Chociaż możesz myśleć o tej lambdzie jako wskaźniku do funkcji, tak nie jest. Nie możesz ponownie przypisać funkcji auto f = [](){ return 17; };
do innej funkcji lub lambdy!
auto f = [](){ return 17; };
f = [](){ return -42; };
powyższe jest niezgodne z prawem . Nie ma miejsca w f
do sklepu , która funkcja będzie się nazywać - te informacje są przechowywane w rodzaju dnia f
, a nie w wartości f
!
Jeśli zrobiłeś to:
int(*f)() = [](){ return 17; };
albo to:
std::function<int()> f = [](){ return 17; };
nie przechowujesz już bezpośrednio lambdy. W obu tych przypadkach f = [](){ return -42; }
jest legalny - więc w tych przypadkach przechowujemy w wartości, której funkcji się odwołujemy f
. I sizeof(f)
nie jest już 1
, ale raczej sizeof(int(*)())
lub większy (zasadniczo powinien mieć rozmiar wskaźnika lub większy, jak się spodziewasz. std::function
Ma minimalny rozmiar sugerowany przez standard (muszą być w stanie przechowywać „wewnątrz siebie” wywołania do pewnego rozmiaru), które jest co najmniej tak duży jak wskaźnik funkcji w praktyce).
W takim int(*f)()
przypadku przechowujesz wskaźnik funkcji do funkcji, która zachowuje się tak, jakbyś wywołał tę lambdę. Działa to tylko dla lambd bezstanowych (tych z pustą []
listą przechwytywania).
W tym std::function<int()> f
przypadku tworzysz std::function<int()>
instancję klasy wymazywania typu, która (w tym przypadku) używa miejsca docelowego new do przechowywania kopii lambda rozmiaru-1 w buforze wewnętrznym (i jeśli przekazano większą lambdę (z większą liczbą stanów ), użyje alokacji sterty).
Przypuszczam, że coś takiego jest prawdopodobnie tym, co myślisz. Że lambda to obiekt, którego typ jest opisany przez jego podpis. W C ++ zdecydowano, że lambdy o zerowym koszcie abstrakcji zostaną zastąpione ręczną implementacją obiektu funkcji. Pozwala to przekazać lambdę do std
algorytmu (lub podobnego) i mieć jej zawartość w pełni widoczną dla kompilatora podczas tworzenia wystąpienia szablonu algorytmu. Gdyby lambda miała podobny typ std::function<void(int)>
, jej zawartość nie byłaby w pełni widoczna, a ręcznie wykonany obiekt funkcyjny mógłby być szybszy.
Celem standaryzacji C ++ jest programowanie wysokopoziomowe bez narzutu w stosunku do ręcznie tworzonego kodu C.
Teraz, gdy rozumiesz, że f
w rzeczywistości jesteś bezpaństwowcem, w twojej głowie powinno pojawić się inne pytanie: lambda nie ma stanu. Dlaczego nie ma rozmiaru 0
?
Oto krótka odpowiedź.
Wszystkie obiekty w C ++ muszą mieć minimalny rozmiar 1 poniżej standardu, a dwa obiekty tego samego typu nie mogą mieć tego samego adresu. Są one połączone, ponieważ tablica typu T
będzie miała elementy sizeof(T)
rozstawione.
Teraz, ponieważ nie ma stanu, czasami nie zajmuje miejsca. Nie może się to zdarzyć, gdy jest „sam”, ale w niektórych sytuacjach może się to zdarzyć. std::tuple
i podobny kod biblioteki wykorzystuje ten fakt. Oto jak to działa:
Ponieważ lambda jest odpowiednikiem klasy z operator()
przeciążeniem, bezstanowe lambdy (z []
listą przechwytywania) są pustymi klasami. Mają sizeof
od 1
. W rzeczywistości, jeśli odziedziczysz po nich (co jest dozwolone!), Nie zajmą miejsca , o ile nie spowoduje to kolizji adresów tego samego typu . (Jest to znane jako optymalizacja pustej podstawy).
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
the sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
is sizeof(int)
(cóż, powyższe jest niedozwolone, ponieważ nie można utworzyć lambdy w nieocenionym kontekście: musisz utworzyć named, auto toy = make_toy(blah);
a następnie zrobić sizeof(blah)
, ale to tylko szum). sizeof([]{std::cout << "hello world!\n"; })
jest nadal 1
(podobne kwalifikacje).
Jeśli stworzymy inny typ zabawki:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
ma dwie kopie lambda. Ponieważ nie mogą mieć tego samego adresu, sizeof(toy2(some_lambda))
jest 2
!
struct
ze znakiemoperator()
)