Według źródeł, które znalazłem, wyrażenie lambda jest zasadniczo implementowane przez kompilator, tworząc klasę z przeciążonym operatorem wywołania funkcji i zmiennymi, do których się odwołujemy jako składowymi. Sugeruje to, że rozmiar wyrażeń lambda jest różny, a przy wystarczającej liczbie zmiennych odwołań, rozmiar może być dowolnie duży .
std::function
Powinien mieć stały rozmiar , ale to musi być w stanie owinąć każdy rodzaj callables, w tym wszelkie lambdas tego samego rodzaju. Jak to jest realizowane? Jeśli std::function
wewnętrznie używa wskaźnika do celu, to co się dzieje, gdy std::function
instancja jest kopiowana lub przenoszona? Czy są zaangażowane jakieś alokacje sterty?
std::function
jakiś czas temu. Zasadniczo jest to klasa uchwytu dla obiektu polimorficznego. Tworzona jest klasa pochodna wewnętrznej klasy bazowej do przechowywania parametrów przydzielonych na stercie - następnie wskaźnik do tego jest przechowywany jako podobiektstd::function
. Uważam, że wykorzystuje liczenie odniesień, takie jakstd::shared_ptr
kopiowanie i przenoszenie.Odpowiedzi:
Implementacja
std::function
może się różnić w zależności od implementacji, ale główną ideą jest to, że używa wymazywania typów. Chociaż można to zrobić na wiele sposobów, możesz sobie wyobrazić, że trywialne (nieoptymalne) rozwiązanie mogłoby wyglądać następująco (uproszczone dla konkretnego przypadku lubstd::function<int (double)>
dla uproszczenia):W tym prostym podejściu
function
obiekt przechowuje tylko aunique_ptr
do typu podstawowego. Dla każdego innego funktora używanego z thefunction
, tworzony jest nowy typ wyprowadzony z bazy i dynamicznie tworzony jest obiekt tego typu.std::function
Obiekt jest zawsze tej samej wielkości i przydzieli miejsca, ile potrzeba dla różnych funktorów w stercie.W rzeczywistości istnieją różne optymalizacje, które zapewniają większą wydajność, ale komplikują odpowiedź. Typ może wykorzystywać małe optymalizacje obiektów, dynamiczne wysyłanie można zastąpić wskaźnikiem swobodnej funkcji, który pobiera funktor jako argument, aby uniknąć jednego poziomu pośrednictwa ... ale idea jest zasadniczo taka sama.
Jeśli chodzi o to, jak
std::function
zachowują się kopie , szybki test wskazuje, że kopie wewnętrznego wywoływalnego obiektu są wykonywane, a nie udostępnianie stanu.Test wskazuje, że
f2
zamiast referencji pobierana jest kopia wywoływalnej jednostki. Jeśli wywoływalna jednostkastd::function<>
byłaby współdzielona przez różne obiekty, wynik programu wynosiłby 5, 6, 7.źródło
std::function
byłaby poprawna, gdyby obiekt wewnętrzny został skopiowany, i nie sądzę, żeby tak było (pomyśl o lambdzie, która przechwytuje wartość i jest zmienna, przechowywana w astd::function
, jeśli stan funkcji został skopiowany, liczba kopiistd::function
wewnątrz standardowego algorytmu może spowodować różne wyniki, co jest niepożądane.std::function
wywoła alokację.Odpowiedź od @David Rodríguez - dribeas jest dobra do demonstrowania usuwania typów, ale nie jest wystarczająco dobra, ponieważ wymazywanie typów obejmuje również sposób kopiowania typów (w tej odpowiedzi obiekt funkcji nie będzie konstruowany przez kopiowanie). Te zachowania są również przechowywane w
function
obiekcie, oprócz danych funktora.Sztuczka zastosowana w implementacji STL z Ubuntu 14.04 gcc 4.8 polega na napisaniu jednej funkcji ogólnej, wyspecjalizowaniu jej w każdym możliwym typie funktora i rzutowaniu jej na uniwersalny typ wskaźnika funkcji. Dlatego informacje o typie są usuwane .
Przygotowałem uproszczoną wersję tego. Mam nadzieję, że to pomoże
W wersji STL są również pewne optymalizacje
construct_f
idestroy_f
miesza się w jeden wskaźnik funkcji (z dodatkowym parametrem, który mówi, co robić), aby zaoszczędzić kilka bajtówunion
, więc gdyfunction
obiekt jest konstruowany ze wskaźnika funkcji, będzie przechowywany bezpośrednio w przestrzeniunion
zamiast stertyMoże implementacja STL nie jest najlepszym rozwiązaniem, ponieważ słyszałem o szybszej implementacji . Uważam jednak, że podstawowy mechanizm jest taki sam.
źródło
W przypadku niektórych typów argumentów („jeśli celem f jest wywoływalny obiekt przekazany przez
reference_wrapper
lub wskaźnik funkcji”),std::function
konstruktor nie zezwala na żadne wyjątki, więc użycie pamięci dynamicznej nie wchodzi w grę. W tym przypadku wszystkie dane muszą być przechowywane bezpośrednio wstd::function
obiekcie.W ogólnym przypadku (w tym przypadku lambda), użycie pamięci dynamicznej (za pośrednictwem standardowego alokatora lub alokatora przekazanego do
std::function
konstruktora) jest dozwolone zgodnie z wymaganiami implementacji. Standard zaleca implementacje, aby nie używać pamięci dynamicznej, jeśli można tego uniknąć, ale jak słusznie mówisz, jeśli obiekt funkcji (niestd::function
obiekt, ale obiekt w nim zawijany) jest wystarczająco duży, nie ma sposobu, aby temu zapobiec, ponieważstd::function
ma stały rozmiar.To uprawnienie do zgłaszania wyjątków jest udzielane zarówno konstruktorowi zwykłemu, jak i konstruktorowi kopiującemu, który dość jawnie zezwala również na dynamiczne alokacje pamięci podczas kopiowania. W przypadku ruchów nie ma powodu, dla którego pamięć dynamiczna byłaby konieczna. Wydaje się, że standard nie zabrania tego wprost i prawdopodobnie nie może tego zabronić, jeśli move może wywołać konstruktor przenoszenia typu opakowanego obiektu, ale powinieneś być w stanie założyć, że jeśli zarówno implementacja, jak i twoje obiekty są rozsądne, przeniesienie nie spowoduje wszelkie przydziały.
źródło
An
std::function
przeciążaoperator()
czyni go obiektu funktora, praca lambda w ten sam sposób. Zasadniczo tworzy strukturę ze zmiennymi składowymi, do których można uzyskać dostęp wewnątrzoperator()
funkcji. Zatem podstawową koncepcją, o której należy pamiętać, jest to, że lambda jest obiektem (nazywanym funktorem lub obiektem funkcji), a nie funkcją. Norma mówi, aby nie używać pamięci dynamicznej, jeśli można tego uniknąć.źródło
std::function
? Oto kluczowe pytanie.std::function
obiekt ma ten sam rozmiar i nie ma rozmiaru zawartych lambd.std::vector<T...>
obiekt ma ustalony rozmiar (copiletime) niezależny od rzeczywistej instancji / liczby elementów alokatora.std::function<void ()> f;
nie ma potrzeby przydzielania tam,std::function<void ()> f = [&]() { /* captures tons of variables */ };
najprawdopodobniej przydziela.std::function<void()> f = &free_function;
prawdopodobnie też nie przydziela ...