Często słyszę o funktorach w C ++. Czy ktoś może mi przedstawić, co to jest iw jakich przypadkach byłby użyteczny?
875
Często słyszę o funktorach w C ++. Czy ktoś może mi przedstawić, co to jest iw jakich przypadkach byłby użyteczny?
operator()(...)
znaczy: przeciąża operatora „wywołania funkcji” . Jest to po prostu przeciążenie operatora dla()
operatora. Nie pomyl sięoperator()
z wywołaniem funkcji o nazwieoperator
, ale postrzegaj ją jako zwykłą składnię przeciążającą operatora.Odpowiedzi:
Funktor to właściwie tylko klasa, która definiuje operator (). Pozwala to tworzyć obiekty, które „wyglądają” jak funkcja:
Jest kilka fajnych rzeczy na temat funktorów. Jednym z nich jest to, że w przeciwieństwie do zwykłych funkcji, mogą zawierać stan. Powyższy przykład tworzy funkcję, która dodaje 42 do wszystkiego, co dasz. Ale ta wartość 42 nie jest zakodowana na stałe, została podana jako argument konstruktora podczas tworzenia naszej instancji funktora. Mógłbym utworzyć kolejny sumator, który dodał 27, po prostu wywołując konstruktor o innej wartości. Dzięki temu można je łatwo dostosowywać.
Jak pokazują ostatnie linie, często przekazujesz funktory jako argumenty do innych funkcji, takich jak std :: transform lub inne standardowe algorytmy biblioteczne. Możesz zrobić to samo ze zwykłym wskaźnikiem funkcji, z wyjątkiem, jak powiedziałem powyżej, funktorów można „dostosować”, ponieważ zawierają one stan, dzięki czemu są bardziej elastyczne (jeśli chciałbym użyć wskaźnika funkcji, musiałbym napisać funkcję która dodała dokładnie 1 do argumentu. Funktor jest ogólny i dodaje wszystko, czym go zainicjalizowałeś), a także potencjalnie bardziej wydajne. W powyższym przykładzie kompilator dokładnie wie, która funkcja
std::transform
powinna zostać wywołana. Powinien zadzwonićadd_x::operator()
. Oznacza to, że może wstawić to wywołanie funkcji. To sprawia, że jest tak samo wydajny, jak gdybym ręcznie wywołał funkcję dla każdej wartości wektora.Gdybym zamiast tego przekazał wskaźnik funkcji, kompilator nie mógłby natychmiast zobaczyć, na którą funkcję wskazuje, więc jeśli nie wykona dość skomplikowanych globalnych optymalizacji, musiałby wyrejestrować wskaźnik w czasie wykonywania, a następnie wykonać wywołanie.
źródło
add42
, użyłbym funktora, który wcześniej stworzyłem i dodałbym 42 do każdej wartości. Zadd_x(1)
utworzyć nową instancję funktora, jeden który tylko dodaje 1 do każdej wartości. To po prostu pokazanie, że często tworzysz funktor „w locie”, kiedy go potrzebujesz, zamiast go najpierw tworzyć i utrzymywać, zanim faktycznie go użyjesz.operator()
, ponieważ to jest to, czego używa rozmówca, aby je wywołać. To, co jeszcze ma funktor funkcji składowych, konstruktorów, operatorów i zmiennych składowych, zależy wyłącznie od Ciebie.add42
nazwany byłby funktorem, a nieadd_x
(która jest klasą funktora lub po prostu klasą funktora). Uważam tę terminologię za spójną, ponieważ funktory są również nazywane obiektami funkcji , a nie klasami funkcji. Czy możesz to wyjaśnić?Mały dodatek. Możesz użyć
boost::function
, aby utworzyć funktory z funkcji i metod, takich jak:i możesz użyć boost :: bind, aby dodać stan do tego funktora
i najbardziej przydatne, dzięki boost :: bind i boost :: function możesz stworzyć funktor z metody klasy, w rzeczywistości jest to delegat:
Możesz utworzyć listę lub wektor funktorów
Jest jeden problem z tymi wszystkimi rzeczami, komunikaty o błędach kompilatora nie są czytelne dla człowieka :)
źródło
operator ()
być publiczne w pierwszym przykładzie, ponieważ zajęcia są domyślnie prywatne?Functor to obiekt, który działa jak funkcja. Zasadniczo klasa, która definiuje
operator()
.Prawdziwą zaletą jest to, że funktor może utrzymać stan.
źródło
int
kiedy powinien powrócićbool
? To jest C ++, a nie C. Kiedy ta odpowiedź została napisana,bool
nie istniała?Nazwa „funktor” była tradycyjnie używana w teorii kategorii na długo przed pojawieniem się C ++ na scenie. Nie ma to nic wspólnego z koncepcją funktora w C ++. Lepiej jest używać obiektu funkcji nazwy zamiast tego, co nazywamy „funktorem” w C ++. W ten sposób inne języki programowania nazywają podobne konstrukcje.
Używany zamiast zwykłej funkcji:
Funkcje:
Cons:
Używany zamiast wskaźnika funkcji:
Funkcje:
Cons:
Używany zamiast funkcji wirtualnej:
Funkcje:
Cons:
źródło
foo(arguments)
. Dlatego może zawierać zmienne; na przykład, jeśli maszupdate_password(string)
funkcję, możesz chcieć śledzić, jak często to się zdarzało; z funktorem, który możeprivate long time
reprezentować znacznik czasu, w którym ostatnio się wydarzyło. Ze wskaźnikiem funkcji lub zwykłą funkcją będziesz musiał użyć zmiennej poza obszarem nazw, co jest bezpośrednio związane tylko z dokumentacją i użytkowaniem, a nie z definicją. LJak inni wspominali, funktor to obiekt, który działa jak funkcja, tzn. Przeciąża operatora wywołania funkcji.
Funktory są powszechnie stosowane w algorytmach STL. Są użyteczne, ponieważ mogą utrzymywać stan przed wywołaniami funkcji i między nimi, jak zamknięcie w językach funkcjonalnych. Na przykład można zdefiniować
MultiplyBy
funktor, który zwielokrotnia jego argument przez określoną kwotę:Następnie możesz przekazać
MultiplyBy
obiekt do algorytmu takiego jak std :: transform:Kolejną zaletą funktora nad wskaźnikiem funkcji jest to, że wywołanie może być wstawiane w większej liczbie przypadków. Jeśli przekazałeś wskaźnik funkcji
transform
, chyba że to wywołanie zostanie wstawione, a kompilator wie, że zawsze przekazujesz mu tę samą funkcję, nie może on wstawić wywołania przez wskaźnik.źródło
Dla początkujących, takich jak ja: po krótkiej analizie dowiedziałem się, co zrobił kod jalf.
Funktor to obiekt klasy lub struktury, który można „wywołać” jak funkcję. Jest to możliwe dzięki przeciążeniu
() operator
.() operator
(Nie wiem, co jego nazwie) może przyjąć dowolną liczbę argumentów. Inni operatorzy biorą tylko dwie, tzn.+ operator
Mogą przyjmować tylko dwie wartości (po jednej z każdej strony operatora) i zwracają dowolną wartość, dla której ją przeciążono. Możesz zmieścić dowolną liczbę argumentów w jednym,() operator
co daje mu elastyczność.Aby stworzyć funktor, najpierw musisz stworzyć swoją klasę. Następnie tworzysz konstruktor do klasy z parametrem wybranego typu i nazwy. Następnie w tej samej instrukcji znajduje się lista inicjalizująca (która korzysta z pojedynczego operatora dwukropka, co również było nowością), która konstruuje obiekty członków klasy z uprzednio zadeklarowanym parametrem dla konstruktora. A później
() operator
jest przeciążony. Na koniec deklarujesz prywatne obiekty klasy lub struktury, które utworzyłeś.Mój kod (uznałem, że nazwy zmiennych Jalfa są mylące)
Jeśli którekolwiek z tych stwierdzeń jest niedokładne lub jest po prostu złe, popraw mnie!
źródło
Funktor jest funkcją wyższego rzędu, która stosuje funkcję do typów sparametryzowanych (tj. Szablonowych). Jest to uogólnienie funkcji wyższego rzędu mapy . Na przykład możemy zdefiniować funktor dla
std::vector
tego:Ta funkcja przyjmuje wartość „a”
std::vector<T>
i zwraca ją,std::vector<U>
gdy otrzyma funkcję „F
a”T
i zwraca wartość aU
. F funktor nie musi być definiowany dla typów kontenerów, można go również zdefiniować dla każdego typu szablonu, w tymstd::shared_ptr
:Oto prosty przykład, który konwertuje typ na
double
:Istnieją dwa prawa, których funktorzy powinni przestrzegać. Pierwszym z nich jest prawo tożsamości, które stanowi, że jeśli funktorowi zostanie przypisana funkcja tożsamości, to powinno być to samo, co zastosowanie funkcji tożsamości do typu, to znaczy
fmap(identity, x)
powinno być takie samo jakidentity(x)
:Kolejnym prawem jest prawo składu, które stwierdza, że jeśli funktorowi zostanie dana kompozycja dwóch funkcji, to powinno być to samo, co zastosowanie funktora do pierwszej funkcji, a następnie do drugiej funkcji. Tak,
fmap(std::bind(f, std::bind(g, _1)), x)
powinna być taka sama, jakfmap(f, fmap(g, x))
:źródło
fmap(id, x) = id(x)
ifmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Oto faktyczna sytuacja, w której byłem zmuszony użyć Functora do rozwiązania mojego problemu:
Mam zestaw funkcji (powiedzmy 20 z nich) i wszystkie są identyczne, z wyjątkiem tego, że każda z nich wywołuje inną określoną funkcję w 3 określonych miejscach.
To niesamowite marnotrawstwo i powielanie kodu. Normalnie przekazałbym wskaźnik funkcji i nazwałbym to w 3 punktach. (Więc kod musi pojawić się tylko raz, a nie dwadzieścia razy.)
Ale potem zdałem sobie sprawę, że w każdym przypadku konkretna funkcja wymagała zupełnie innego profilu parametrów! Czasem 2 parametry, czasem 5 parametrów itp.
Innym rozwiązaniem byłoby posiadanie klasy bazowej, w której określona funkcja jest przesłoniętą metodą w klasie pochodnej. Ale czy naprawdę chcę zbudować całe to DZIEDZICTWO, tylko po to, aby przekazać wskaźnik funkcji ????
ROZWIĄZANIE: Więc stworzyłem klasę opakowania („Functor”), która jest w stanie wywoływać dowolne funkcje, które musiałem wywołać. Ustawiam go wcześniej (z jego parametrami itp.), A następnie przekazuję go zamiast wskaźnika funkcji. Teraz wywoływany kod może uruchomić Functor, nie wiedząc, co dzieje się wewnątrz. Może nawet zadzwonić kilka razy (potrzebowałem, żeby zadzwonić 3 razy).
To wszystko - praktyczny przykład, w którym Functor okazał się oczywistym i łatwym rozwiązaniem, które pozwoliło mi zmniejszyć duplikację kodu z 20 funkcji do 1.
źródło
Oprócz funkcji wywołania zwrotnego funktory C ++ mogą również pomóc w zapewnieniu Matlabowi stylu dostępu podobnego do klasy macierzy . Jest przykład .
źródło
operator()
ale nie wykorzystaniem właściwości obiektu funkcji.Podobnie jak zostało to powtórzone, funktory to klasy, które można traktować jako funkcje (operator przeciążenia ()).
Są najbardziej przydatne w sytuacjach, w których trzeba powiązać niektóre dane z powtarzającymi się lub opóźnionymi wywołaniami funkcji.
Na przykład, połączona lista funktorów może być wykorzystana do implementacji podstawowego niskonakładowego synchronicznego systemu koroutynowego, programu rozsyłającego zadania lub analizy plików, którą można przerwać. Przykłady:
Oczywiście te przykłady same w sobie nie są tak przydatne. Pokazują tylko, jak funktory mogą być użyteczne, same funktory są bardzo podstawowe i nieelastyczne, co czyni je mniej użytecznymi niż, na przykład, to, co zapewnia boost.
źródło
W gtkmm używane są funktory do połączenia przycisku GUI z rzeczywistą funkcją lub metodą C ++.
Jeśli użyjesz biblioteki pthread do wielowątkowej aplikacji, Functors może ci pomóc.
Aby rozpocząć wątek, jednym z argumentów tego wskaźnika
pthread_create(..)
jest wskaźnik funkcji, który należy wykonać na jego własnym wątku.Ale jest jedna niedogodność. Ten wskaźnik nie może być wskaźnikiem do metody, chyba że jest to metoda statyczna lub jeśli nie określono jej klasy , jak
class::method
. I kolejną rzeczą, interfejs twojej metody może być tylko:Więc nie możesz uruchamiać (w prosty, oczywisty sposób) metod z twojej klasy w wątku bez robienia czegoś dodatkowego.
Bardzo dobrym sposobem radzenia sobie z wątkami w C ++ jest tworzenie własnej
Thread
klasy. Jeśli chcesz uruchomić metody zMyClass
klasy, to co zrobiłem, przekształć te metody wFunctor
klasy pochodne.Również
Thread
klasa ma tę metodę:static void* startThread(void* arg)
Wskaźnik do tej metody zostaną wykorzystane jako argument do rozmowy
pthread_create(..)
. A to, costartThread(..)
powinno otrzymać w arg, tovoid*
rzutowane odwołanie do instancji w stercie dowolnejFunctor
klasy pochodnej, które zostanie rzutowane z powrotemFunctor*
po wykonaniu, a następnie nazwane jegorun()
metodą.źródło
Aby dodać, użyłem obiektów funkcyjnych, aby dopasować istniejącą starszą metodę do wzorca poleceń; (tylko miejsce, w którym czułem piękno OO prawdziwego OCP); Dodając tutaj również odpowiedni wzorzec adaptera funkcji.
Załóżmy, że twoja metoda ma podpis:
Zobaczymy, jak możemy dopasować go do wzorca poleceń - w tym celu musisz najpierw napisać adapter funkcji członka, aby można go było wywołać jako obiekt funkcji.
Uwaga - jest to brzydkie i może być możliwe, że możesz użyć pomocników wiązania wzmocnienia, itp., Ale jeśli nie możesz lub nie chcesz, jest to jeden ze sposobów.
Potrzebujemy również metody pomocniczej mem_fun3 dla powyższej klasy, aby pomóc w wywołaniu.
}
Teraz, aby powiązać parametry, musimy napisać funkcję spoiwa. Oto on:
I funkcja pomocnicza do użycia klasy binder3 - bind3:
Teraz musimy użyć tego z klasą Command; użyj następującego typedef:
Oto jak to nazywasz:
Uwaga: f3 (); wywoła metodę task1-> ThreeParameterTask (21,22,23) ;.
Pełny kontekst tego wzorca pod poniższym linkiem
źródło
Dużą zaletą implementacji funkcji jako funktorów jest to, że mogą utrzymywać i ponownie wykorzystywać stan między połączeniami. Na przykład wiele algorytmów programowania dynamicznego, takich jak algorytm Wagnera-Fischera do obliczania odległości Levenshteina między łańcuchami, działa poprzez wypełnienie dużej tabeli wyników. Przydzielanie tej tabeli przy każdym wywołaniu funkcji jest bardzo nieefektywne, więc implementacja funkcji jako funktora i uczynienie tabeli zmienną składową może znacznie poprawić wydajność.
Poniżej znajduje się przykład implementacji algorytmu Wagnera-Fischera jako funktora. Zwróć uwagę, w jaki sposób tabela jest przydzielana w konstruktorze, a następnie wykorzystywana ponownie
operator()
, z koniecznością zmiany rozmiaru.źródło
Funkcję Functor można także wykorzystać do symulacji zdefiniowania funkcji lokalnej w funkcji. Zobacz pytanie i inne .
Ale lokalny funktor nie może uzyskać dostępu poza zmiennymi automatycznymi. Funkcja lambda (C ++ 11) jest lepszym rozwiązaniem.
źródło
„Odkryłem” bardzo interesujące użycie funktorów: używam ich, gdy nie mam dobrej nazwy dla jednej metody, ponieważ funktor jest metodą bez nazwy ;-)
źródło