2013-04-19 6 views

Odpowiedz

33

wersji podstawowej, do stosowania w pliku nagłówka:

template<typename Lambda> 
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better 
    if(Arg1 > 0){ 
    return Arg2(Arg1); 
    } else { 
    return false; // remember, all control paths must return a value 
    } 
} 

Bardziej skomplikowana wersja, jeśli chcesz podzielić interfejsu od implementacji (to prowadzi koszty czas):

bool Func1(int Arg1, std::function<bool(int)> Arg2){ 
    if(Arg1 > 0){ 
    return Arg2(Arg1); 
    } else { 
    return false; // remember, all control paths must return a value 
    } 
} 

std::function używa typu skasowaniu stworzyć niestandardowym owijkę wokół lambda i następnie wyświetla interfejs inny niż wirtualny, który używa wzorca pImpl, aby przekazać go do niestandardowego opakowania.

Lub, w warunkach mniej technicznych, std::function<bool(int)> jest klasa, które można zawinąć prawie wszystko, co można nazwać jak funkcja, przekazując jeden parametr, który jest kompatybilny z przepuszczanie int i zwraca coś, co jest zgodne z zwracając numer bool.

Rozmowa przez std::function ma koszt czasu prowadzony w przybliżeniu równą rozmowy virtual funkcyjnego (spowodowane przez wyżej typu skasowaniem), a podczas jego tworzenia musi skopiować stan obiektu funkcyjnego (aka funktora) upłynął w (które mogą być tanie - bezpaństwowe lambdas lub lambdas przechwytujące argumenty przez odniesienie - lub drogie w niektórych innych przypadkach) i przechowujące je (zazwyczaj w wolnym sklepie lub kupie, który ma koszt), podczas gdy wersje czysto szablonowe może być "podkreślony" w momencie wywołania (tj. może nie tylko kosztować mniej niż wywołanie funkcji, kompilator może nawet zoptymalizować ponad granicami wywołania funkcji i powrotu!)

Fantazyjna wersja pierwszego przykładu, który również lepiej radzi sobie z niektórymi przypadkami narożnymi: (również musi być i mplemented w pliku nagłówka, lub w tej samej jednostce tłumaczeniowej, ponieważ jest wykorzystywany)

template<typename Lambda> 
bool Func1(int Arg1, Lambda&& Arg2){ 
    if(Arg1 > 0){ 
    return std::forward<Lambda>(Arg2)(Arg1); 
    } else { 
    return false; // remember, all control paths must return a value 
    } 
} 

który wykorzystuje technikę znaną jako „doskonały” spedycji.Dla niektórych funktorów generuje to nieco inne zachowanie niż # 1 (i zazwyczaj bardziej poprawne zachowanie).

Większość poprawy pochodzi tworzą użycie && w liście argumentów: oznacza to, że odniesienie do funktora jest przekazywana (zamiast kopii), oszczędzając pewne koszty i pozwala zarówno const lub nie const funktor być przekazywane w.

zmiana std::forward<Lambda>(...) spowodowałoby jedynie zmianę w zachowaniu, czy ktoś używał stosunkowo nową funkcję C++, który umożliwia metod (w tym operator()), aby zastąpić na rvalue/lwartości status wskaźnika this. Teoretycznie może to być przydatne, ale liczba funktorów, które widziałem, które faktycznie przesłoniły w oparciu o stan rvalue this jest 0. Kiedy piszę poważny kod biblioteczny (tm) idę na to, ale rzadko inaczej.

Jest jeszcze jedna możliwa rzecz do rozważenia. Załóżmy, że chcesz wziąć albo funkcję, która zwraca bool, albo funkcję, która zwraca void, i jeśli funkcja zwraca void chcesz traktować ją tak, jakby wróciła true. Jako przykład bierzesz funkcję, która jest wywoływana podczas iteracji nad pewną kolekcją, i chcesz opcjonalnie wspomóc wczesne zatrzymanie. Funkcja zwraca false, gdy chce się zatrzymać przedwcześnie, i true lub void w przeciwnym razie.

Albo, w bardziej ogólnym przypadku, jeśli masz kilka nadpisań funkcji, z których jedna przyjmuje funkcję, a inne przyjmują inny typ w tym samym miejscu.

Jest to możliwe, o ile się tu dostanę (za pomocą inteligentnego adaptera lub za pomocą technik SFINAE). Jednak prawdopodobnie lepiej będzie po prostu utworzyć dwie różne funkcje, ponieważ wymagane techniki są zbyt duże.


Technicznie std::function może użyć magicznego pyłu bajki robić to, co robi, a jego zachowanie jest opisana w normie, a nie jego realizację. Opisuję prostą implementację, która jest zbliżona do zachowania implementacji std::function, z którą współpracowałem.

+0

Dziękuję, to jest bardzo kompletne. +1 –

+0

Wierzę, że można również przekazać funkcję "const std :: function & func". Czy jest lepiej/gorzej niż ruch i ... rzecz? Zawsze myślałem, że "const &" oznacza po prostu "przekazanie tego obiektu", ale && oznacza przeniesienie stanu obiektu do nowej instancji (która może wywołać ctor kopii ruchu?) –

+1

@AlexPetrenko '&&' jest zwykle wartością rvalue, ale jest również używany w technice zwanej ["perfect forwarding"] (http://stackoverflow.com/questions/3582001/advantages-of-using-forward)/["universal references"] (http://stackoverflow.com/ questions/14302849/syntax-for-universal-references), gdzie 'T &&' może być wartością rwartującą ** lub ** wartość odniesienia l (w kontekście dedukcji typu - technicznie w dowolnym miejscu, ale rzadko jest używana poza odliczeniem typu w ten sposób). – Yakk

15

Pierwsze rozwiązanie:

Można dokonać funkcję Func1() funkcją szablon:

template<typename T> 
bool Func1(int Arg1, T&& Arg2){ 
    if(Arg1 > 0){ 
     return Arg2(Arg1); 
    } 

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING 
        //  FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING 
        //  OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING 
} 

Następnie można wywołać go jak chcesz:

int main() 
{ 
    Func1(12, [](int D) -> bool { return D < 0; }); 
} 

drugie rozwiązanie:

Jeśli nie chcesz korzystać z szablonów, alternatywny (który przyniesie jakiś run-time narzut) jest użycie std::function:

#include <functional> 

bool Func1(int Arg1, std::function<bool(int)> Arg2){ 
    if(Arg1 > 0){ 
     return Arg2(Arg1); 
    } 

    return false; 
} 

Po raz kolejny, to pozwolisz zadzwonić Func1() sposób pragnienie:

int main() 
{ 
    Func1(12, [](int D) -> bool { return D < 0; }); 
} 
+0

+1: 4 sekundy za wolne w mojej odpowiedzi. Technicznie powinieneś użyć 'std :: forward (Arg2) (Arg1)', ale rzadko jest to warte kłopotów. – Yakk

+2

@Yakk: Cóż, to nie ma znaczenia, kiedy wywoływana jest funkcja lambda lub funkcja, prawda? W końcu nic nie przekazujesz. To * może * zrobić jakąś różnicę z funktorem, na wypadek gdyby funktor używał odniesień do 'this' (jako kwalifikator funkcji, mam na myśli' operator() (...) && ') - ale ... heh, nie warto denerwować IMO :) –

+1

Teoretycznie albo '[myvec] (int x) {myvec.push_back (x); return myvec; } 'lub ręczny odpowiednik może zdawać sobie sprawę, że jest oceniany w kontekście' rvalue' i return-by-move. Ale tak, to jest dość cokolwiek niezrozumiałe. – Yakk

8

Dla tych, których gust jest bardziej tradycyjny, należy pamiętać, że niezapisujące lambdy mogą zostać przekształcone w wskaźniki funkcji. Więc można napisać funkcję powyżej jako:

bool Func1(int Arg1, bool (*Arg2)(int)) { ... } 

i będzie działać poprawnie zarówno dla tradycyjnych funkcji i lambda.

+0

za pomocą szablonu pozwala na przechwytywanie wskaźników funkcyjnych, odwołań do funkcji, obiektów funkcyjnych, lambd z uchwyceniem lub bez), std :: function, wyników std :: bind, itp. – bstamour

+0

Używanie szablonu również nie zadziała, jeśli chcesz przekazać istniejącą wcześniej funkcję lub potrzebować współdziałania z kodem C. Niezbyt zainteresowany czystością strony C++; był to po prostu trochę języka, który według mnie był pominięty przez istniejące odpowiedzi. –

+0

Otrzymuję argument interfejsu C, ale szablony będą całkowicie wiązały się z istniejącą wcześniej funkcją. Jeśli Twój obiekt może być wywołany jak funkcja, to szablon będzie działał. – bstamour

Powiązane problemy