2013-08-21 10 views
97

Powiedzmy Mam funkcję, która bierze std::function:Czy powinienem przekazać funkcję std :: przez const-reference?

void callFunction(std::function<void()> x) 
{ 
    x(); 
} 

powinienem przekazać x przez const odniesień zamiast ?:

void callFunction(const std::function<void()>& x) 
{ 
    x(); 
} 

Czy odpowiedź na tę zmianę zapytania w zależności od funkcji z nią robi? Na przykład, jeśli jest to funkcja lub konstruktor składowy klasy, który przechowuje lub inicjuje std::function do zmiennej składowej.

+1

Prawdopodobnie nie. Nie wiem na pewno, ale spodziewałbym się, że 'sizeof (std :: function)' będzie nie większy niż '2 * sizeof (size_t)', który jest najmniejszym rozmiarem, jaki kiedykolwiek rozważałeś dla odniesienia do const . –

+9

@Mats: Nie sądzę, że rozmiar 'std :: function' wrapper jest tak samo ważny jak złożoność kopiowania. W przypadku kopii głębokich może to być znacznie droższe niż sugeruje 'sizeof'. –

+0

Czy należy "przenieść" tę funkcję? – Yakk

Odpowiedz

26

Jeśli martwisz się wydajnością i nie definiujesz funkcji wirtualnego elementu, najprawdopodobniej w ogóle nie powinieneś używać std::function.

Tworzenie typu funktora Parametr szablonu pozwala na większą optymalizację niż std::function, włączając w to wstawianie logiki funktora. Efekt tych optymalizacji prawdopodobnie znacznie przewyższa obawy dotyczące kopiowania w stosunku do nieokreślonego, dotyczące przekazywania std::function.

Szybsze:

template<typename Functor> 
void callFunction(Functor&& x) 
{ 
    x(); 
} 
+0

Nie martwię się w ogóle o wydajność.Pomyślałem, że używanie referencji const tam, gdzie powinny być używane, jest powszechną praktyką (ciągi i wektory przychodzą na myśl). –

+9

@Ben: Myślę, że najnowocześniejszym sposobem implementacji tego jest użycie 'std :: forward (x)();', aby zachować kategorię wartości funktora, ponieważ jest to "uniwersalny" odnośnik. W 99% przypadków nie ma jednak znaczenia. – GManNickG

+0

Fajna, nie wiedziałem, że to pozwoli na działanie Lambda. –

19

Jak zwykle w C++ 11, przechodząc przez wartości/odniesienia/const odniesień zależy od tego, co zrobić ze swoim argumencie. std::function nie jest inaczej.

Przekazywanie przez wartość pozwala przenieść argument do zmiennej (zazwyczaj zmiennych członkiem klasy):

struct Foo { 
    Foo(Object o) : m_o(std::move(o)) {} 

    Object m_o; 
}; 

Kiedy wiesz czynność przeniesie swój argument, to jest to najlepsze rozwiązanie ten sposób użytkownicy mogą kontrolować, jak nazywają swoją funkcję:

Foo f1{Object()};    // move the temporary, followed by a move in the constructor 
Foo f2{some_object};   // copy the object, followed by a move in the constructor 
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor 

wierzę już wiesz semantyki (nie) const odniesień, więc nie będę okładać punkt. Jeśli potrzebujesz więcej informacji na ten temat, po prostu zapytaj, a zaktualizuję.

54

Jeśli chcesz osiągnąć wydajność, podaj wartość, jeśli ją przechowujesz.

Załóżmy, że masz funkcję o nazwie "uruchom to w wątku UI".

std::future<void> run_in_ui_thread(std::function<void()>) 

który działa kod, w „Ul” wątek, a następnie sygnalizuje future po zakończeniu. (Przydatne w ramach UI gdzie wątek UI jest, gdy ci mają bałagan z elementów UI)

Mamy dwa podpisy Rozważamy:

std::future<void> run_in_ui_thread(std::function<void()>) // (A) 
std::future<void> run_in_ui_thread(std::function<void()> const&) // (B) 

Teraz mogą z nich korzystać w sposób następujący:

run_in_ui_thread([=]{ 
    // code goes here 
}).wait(); 

który tworzy anonimowego zamknięcie (lambda) skonstruować std::function z niego, przekazać go do funkcji run_in_ui_thread, a następnie czekać na zakończenie pracy w głównym wątku.

W przypadku (A), std::function jest bezpośrednio skonstruowany z naszej lambda, która jest następnie używana w ramach run_in_ui_thread. Wartość lambda to move d do std::function, więc każdy ruchomy stan jest skutecznie przenoszony do niej.

W drugim przypadku, tymczasowy std::function jego tworzenia, lambda move d do niego, to tymczasowa std::function jest stosowany jako odniesienie w run_in_ui_thread.

Jak dotąd, tak dobrze - obaj wykonują identycznie. Z wyjątkiem run_in_ui_thread zrobi kopię swojego argumentu funkcji do wysłania do wątku interfejsu użytkownika do wykonania! (powróci, zanim zostanie zrobione, więc nie może po prostu użyć odniesienia do niego). Dla przypadku (A), po prostu move z std::function do jego długoterminowego przechowywania. W przypadku (B) jesteśmy zmuszeni skopiować std::function.

Ten sklep sprawia, że ​​przekazywanie według wartości jest bardziej optymalne. Jeśli istnieje możliwość zapisania kopii pliku std::function, podaj wartość. W przeciwnym razie, każda z nich jest mniej więcej równoważna: jedyną wadą wartości dodanej jest to, że używasz tego samego nieporęcznego obiektu std::function i używasz jednej metody podrzędnej za drugą. Poza tym, move będzie tak wydajny jak const&.

Teraz są pewne inne różnice między tymi dwoma, które najczęściej wskakują, jeśli mamy stały stan w ramach std::function.

Załóżmy, że std::function przechowuje jakiś obiekt z operator() const, ale ma również pewne elementy danych, które modyfikuje (jakże nieuprzejmość!).

W przypadku std::function<> const& zmodyfikowane zmienne danych mutable będą propagować poza wywołaniem funkcji. W przypadku std::function<> nie będą.

To stosunkowo dziwny przypadek narożny.

Chcesz traktować std::function tak jak każdy inny potencjalnie ciężki, tani, przenośny. Przenoszenie jest tanie, kopiowanie może być kosztowne.

+0

Zaletą semantyczną "przekazania przez wartość, jeśli je przechowujesz", jak mówisz, jest to, że przez kontrakt funkcja nie może zachować adresu przekazanego argumentu. Ale czy to prawda, że ​​"gdyby nie to, ruch byłby równie skuteczny jak const"? Zawsze widzę koszt operacji kopiowania plus koszt operacji przeniesienia. Po przejściu przez 'const &' widzę tylko koszt operacji kopiowania. – ceztko

+1

@ceztko W obu przypadkach (A) i (B) tymczasowa 'std :: function' jest tworzona z lambda. W (A) tymczasowy jest przenoszony do argumentu "run_in_ui_thread". W (B) odniesienie do wspomnianego tymczasowego jest przekazywane do 'run_in_ui_thread'. Tak długo, jak twoje 'std :: function's są tworzone z lambdas jako temporaries, klauzula ta zawiera. Poprzedni akapit dotyczy przypadku, w którym funkcja 'std :: function' trwa. Jeśli nie * przechowujemy, tworząc po prostu z lambda, 'function const &' i 'function' mają dokładnie taki sam narzut. – Yakk

+0

Ah, widzę! To oczywiście zależy od tego, co dzieje się poza 'run_in_ui_thread()'. Czy jest tylko podpis, który mówi "Przekaż przez referencję, ale nie będę przechowywać adresu"? – ceztko

Powiązane problemy