Powiedzmy, że mam funkcję, która przyjmuje std::function
:
void callFunction(std::function<void()> x)
{
x();
}
Czy powinienem x
zamiast tego przejść przez const-reference ?:
void callFunction(const std::function<void()>& x)
{
x();
}
Czy odpowiedź na to pytanie zmienia się w zależności od tego, co robi z nią funkcja? Na przykład, jeśli jest to funkcja składowa klasy lub konstruktor, który przechowuje lub inicjuje std::function
w zmiennej składowej.
sizeof(std::function)
się, że nie będzie więcej niż2 * sizeof(size_t)
, czyli najmniejszy rozmiar, jaki kiedykolwiek wziąłbyś pod uwagę jako odniesienie do stałej.std::function
opakowania był tak ważny, jak złożoność jego kopiowania. Jeśli w grę wchodzą głębokie kopie, może to być znacznie droższe niżsizeof
sugeruje.move
pełnić tę funkcję?operator()()
jestconst
więc odwołanie do const powinno działać. Ale nigdy nie użyłem std :: function.Odpowiedzi:
Jeśli chcesz wydajności, podaj wartość, jeśli ją przechowujesz.
Załóżmy, że masz funkcję o nazwie „uruchom to w wątku interfejsu użytkownika”.
który uruchamia kod w wątku „ui”, a następnie sygnalizuje zakończenie
future
. (Przydatne w strukturach UI, w których wątek UI jest miejscem, w którym powinieneś zadzierać z elementami UI)Rozważamy dwa podpisy:
Teraz prawdopodobnie użyjemy ich w następujący sposób:
co utworzy anonimowe zamknięcie (lambda), skonstruuje z niego
std::function
wyjście, przekaże je dorun_in_ui_thread
funkcji, a następnie zaczeka, aż zakończy się działanie w głównym wątku.W przypadku (A),
std::function
jest on konstruowany bezpośrednio z naszej lambdy, która jest następnie używana wrun_in_ui_thread
. Lambda jestmove
d dostd::function
, więc każdy ruchomy stan jest skutecznie przenoszony do niej.W drugim przypadku
std::function
tworzony jest tymczasowy , lambda jestmove
w nim d, a następnie ten tymczasowystd::function
jest używany przez odniesienie wrun_in_ui_thread
.Jak na razie dobrze - obaj grają identycznie. Tyle
run_in_ui_thread
że program utworzy kopię swojego argumentu funkcji do wysłania do wątku interfejsu użytkownika w celu wykonania! (wróci, zanim zostanie z nim zakończony, więc nie może po prostu użyć odniesienia do niego). W przypadku (A), po prostu do jego długotrwałego przechowywania. W przypadku (B) jesteśmy zmuszeni skopiować plik .move
std::function
std::function
Ten sklep sprawia, że przekazywanie wartości jest bardziej optymalne. Jeśli istnieje możliwość, że przechowujesz kopię pliku
std::function
, podaj wartość. W przeciwnym razie obie metody są z grubsza równoważne: jedyną wadą wartości bocznej jest to, że bierzesz tę samą masęstd::function
i jedną metodę podrzędną po drugiej. Poza tym, amove
będzie tak samo wydajne jakconst&
.Teraz są pewne inne różnice między tymi dwoma, które najczęściej pojawiają się, jeśli mamy trwały stan w obrębie
std::function
.Załóżmy, że
std::function
przechowuje jakiś obiekt z aoperator() const
, ale ma też kilkamutable
członków danych, które modyfikuje (jakie niegrzeczne!).W takim
std::function<> const&
przypadkumutable
zmodyfikowane elementy składowe danych będą propagowane poza wywołanie funkcji. W takimstd::function<>
przypadku nie będą.To stosunkowo dziwny przypadek narożny.
Chcesz traktować
std::function
jak każdy inny potencjalnie ciężki, tani przenośny typ. Przenoszenie jest tanie, kopiowanie może być kosztowne.źródło
const&
widzę tylko koszt operacji kopiowania.std::function
jest tworzona z lambda. W (A), tymczasowy jest usuwany do argumenturun_in_ui_thread
. W (B) odniesienie do wspomnianego tymczasowego jest przekazywane dorun_in_ui_thread
. Dopóki twojestd::function
s są tworzone z lambd jako tymczasowych, ta klauzula obowiązuje. Poprzedni akapit dotyczy przypadku, w którymstd::function
problem się utrzymuje. Jeśli nie przechowujemy, po prostu tworzymy z lambdafunction const&
ifunction
mamy dokładnie ten sam narzut.run_in_ui_thread()
. Czy jest tylko podpis mówiący „Przekaż przez odniesienie, ale nie będę przechowywać adresu”?std::future<void> run_in_ui_thread( std::function<void()>&& )
Jeśli martwisz się o wydajność i nie definiujesz wirtualnej funkcji składowej, najprawdopodobniej w ogóle nie powinieneś jej używać
std::function
.Uczynienie typu funktora parametrem szablonu pozwala na większą optymalizację niż
std::function
włączenie logiki funktora. Efekt tych optymalizacji prawdopodobnie znacznie przeważy nad obawami o kopiowanie kontra pośrednie, jak przejśćstd::function
.Szybciej:
źródło
std::forward<Functor>(x)();
, aby zachować kategorię wartości funktora, ponieważ jest to odniesienie „uniwersalne”. Nie będzie to jednak miało znaczenia w 99% przypadków.callFunction(std::move(myFunctor));
std::move
jeśli nie będziesz już jej potrzebować w inny sposób, lub przekazać bezpośrednio, jeśli nie chcesz wyjść z istniejącego obiektu. Reguły zwijania odwołań zapewniają, żecallFunction<T&>()
parametr ma typT&
, a nieT&&
.Jak zwykle w C ++ 11, przekazywanie przez wartość / referencję / stałą-referencję zależy od tego, co zrobisz z argumentem.
std::function
nie jest inaczej.Przekazywanie przez wartość umożliwia przeniesienie argumentu do zmiennej (zwykle jest to zmienna składowa klasy):
Jeśli wiesz, że Twoja funkcja przeniesie swój argument, jest to najlepsze rozwiązanie, w ten sposób użytkownicy mogą kontrolować sposób wywoływania funkcji:
Wydaje mi się, że znasz już semantykę (nie) odniesień stałych, więc nie będę się nad tym rozwodzić. Jeśli chcesz, żebym dodał więcej wyjaśnień na ten temat, po prostu zapytaj, a zaktualizuję.
źródło