Jakie są funktory C ++ i ich zastosowania?


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?


4
Temat ten został omówiony w odpowiedzi na to pytanie: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille

2
Służy do utworzenia zamknięcia w C ++.
copper.hat

Patrząc na poniższe odpowiedzi, jeśli ktoś zastanawia się, co to 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 nazwie operator, ale postrzegaj ją jako zwykłą składnię przeciążającą operatora.
zardosht

Odpowiedzi:


1041

Funktor to właściwie tylko klasa, która definiuje operator (). Pozwala to tworzyć obiekty, które „wyglądają” jak funkcja:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

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::transformpowinna 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.


32
Czy możesz wyjaśnić tę linię, proszę std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); dlaczego piszesz tam add_x, a nie add42?
Alecs

102
@Alecs Oba działałyby (ale efekt byłby inny). Gdybym użył add42, użyłbym funktora, który wcześniej stworzyłem i dodałbym 42 do każdej wartości. Z add_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.
czerwiec

8
@zadane oczywiście. Muszą tylko mieć 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.
lipiec

4
@ rikimaru2013 W języku programowania funkcjonalnego masz rację, funkcja jest również funktorem, ale w języku C ++ funktor jest w szczególności klasą używaną jako funkcja. Na początku terminologia była nieco nadużywana, ale podział ten jest przydatnym rozróżnieniem i tak jest do dziś. Jeśli zaczniesz nazywać funkcje „funktorami” w kontekście C ++, po prostu pomylisz rozmowę.
srm

6
Czy to klasa czy instancja klasy? W większości źródeł add42nazwany byłby funktorem, a nie add_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ć?
Siergiej Tachenov

121

Mały dodatek. Możesz użyć boost::function, aby utworzyć funktory z funkcji i metod, takich jak:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

i możesz użyć boost :: bind, aby dodać stan do tego funktora

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

i najbardziej przydatne, dzięki boost :: bind i boost :: function możesz stworzyć funktor z metody klasy, w rzeczywistości jest to delegat:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Możesz utworzyć listę lub wektor funktorów

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

Jest jeden problem z tymi wszystkimi rzeczami, komunikaty o błędach kompilatora nie są czytelne dla człowieka :)


4
Czy nie powinno operator ()być publiczne w pierwszym przykładzie, ponieważ zajęcia są domyślnie prywatne?
NathanOliver

4
może w pewnym momencie ta odpowiedź zasługuje na aktualizację, ponieważ teraz lambdy są najłatwiejszym sposobem na uzyskanie funktora z dowolnego
źródła

102

Functor to obiekt, który działa jak funkcja. Zasadniczo klasa, która definiuje operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Prawdziwą zaletą jest to, że funktor może utrzymać stan.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

11
Wystarczy dodać, że można ich używać tak jak wskaźnika funkcji.
Martin York,

7
@LokiAstari - Dla tych, którzy są nowi w koncepcji, może to być nieco mylące. Funkcje mogą być „używane jak”, ale nie zawsze „zamiast” wskaźników funkcji. Na przykład funkcja, która pobiera wskaźnik funkcji, nie może zastąpić funktora na swoim miejscu, nawet jeśli funktor ma te same argumenty i zwraca wartość jak wskaźnik funkcji. Ogólnie rzecz biorąc, podczas projektowania funktory są preferowaną i teoretycznie „bardziej nowoczesną” drogą.
MasonWinsauer,

Dlaczego drugi powraca, intkiedy powinien powrócić bool? To jest C ++, a nie C. Kiedy ta odpowiedź została napisana, boolnie istniała?
Pozew Fund Moniki w

@QPaysTaxes Chyba literówka. Prawdopodobnie skopiowałem i wkleiłem kod z pierwszego przykładu i zapomniałem go zmienić. Naprawiłem to teraz.
James Curran

1
@Riasat Jeśli Matcher znajduje się w bibliotece, zdefiniowanie Is5 () jest dość proste. I możesz utworzyć Is7 (), Is32 () itp. Ponadto, to tylko przykład. Funktor może być znacznie bardziej skomplikowany.
James Curran

51

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:

  • Obiekt funkcji może mieć stan
  • Obiekt funkcji pasuje do OOP (zachowuje się jak każdy inny obiekt).

Cons:

  • Przynosi większą złożoność do programu.

Używany zamiast wskaźnika funkcji:

Funkcje:

  • Obiekt funkcji często może być wstawiany

Cons:

  • Obiekt funkcji nie może być zamieniony z innym typem obiektu funkcji podczas działania (przynajmniej chyba, że ​​rozszerzy on pewną klasę podstawową, co powoduje pewne obciążenie)

Używany zamiast funkcji wirtualnej:

Funkcje:

  • Obiekt funkcji (niewirtualny) nie wymaga wywoływania vtable i środowiska wykonawczego, dlatego w większości przypadków jest bardziej wydajny

Cons:

  • Obiekt funkcji nie może być zamieniony z innym typem obiektu funkcji podczas działania (przynajmniej chyba, że ​​rozszerzy on pewną klasę podstawową, co powoduje pewne obciążenie)

1
Czy możesz wyjaśnić te przypadki użycia na prawdziwym przykładzie? jak możemy użyć funktorów jako polimorfizmu i wskaźnika funkcji?
Milad Khajavi

1
Co właściwie oznacza, że ​​funktor utrzymuje stan?
erogol

dzięki za zwrócenie uwagi na to, że potrzebna jest klasa podstawowa, aby mieć jakiś polimorfizm. Mam tylko problem, że muszę użyć funktora w tym samym miejscu, co prosty wskaźnik funkcji, i jedynym sposobem, jaki znalazłem, było napisanie podstawowej klasy funktora (ponieważ nie mogę używać rzeczy w C ++ 11). Nie byłem pewien, czy ten narzut ma sens, dopóki nie przeczytam twojej odpowiedzi.
idclev 463035818,

1
@Erogol Funktor to obiekt obsługujący składnię foo(arguments). Dlatego może zawierać zmienne; na przykład, jeśli masz update_password(string)funkcję, możesz chcieć śledzić, jak często to się zdarzało; z funktorem, który może private long timereprezentować 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ą. L
Pozew Fund Moniki

4
For¹ za wspomnienie, że nazwa została wymyślona bez powodu. Właśnie szukałem relacji między funktorem matematycznym (lub funkcjonalnym, jeśli chcesz) a funktorem z C ++.
Cześć Anioł

41

Jak 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ć MultiplyByfunktor, który zwielokrotnia jego argument przez określoną kwotę:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Następnie możesz przekazać MultiplyByobiekt do algorytmu takiego jak std :: transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

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.


37

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. + operatorMogą 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, () operatorco 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)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Jeśli którekolwiek z tych stwierdzeń jest niedokładne lub jest po prostu złe, popraw mnie!


1
Operator () nazywany jest operatorem wywołania funkcji. Myślę, że można nazwać to również operatorem nawiasowym.
Gautam,

4
„Ten parametr jest w rzeczywistości argumentem„ parameterVar ”przekazanym przez konstruktora, który właśnie napisaliśmy„ Hę?
Wyścigi lekkości na orbicie

22

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::vectortego:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Ta funkcja przyjmuje wartość „a” std::vector<T>i zwraca ją, std::vector<U>gdy otrzyma funkcję „ Fa” Ti zwraca wartość a U. F funktor nie musi być definiowany dla typów kontenerów, można go również zdefiniować dla każdego typu szablonu, w tym std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Oto prosty przykład, który konwertuje typ na double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

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 jak identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

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, jak fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

2
Artykuł argumentujący, że funktor powinien być poprawnie używany w tym znaczeniu (patrz także en.wikipedia.org/wiki/Functor ) i że używanie go do obiektów funkcyjnych jest po prostu niechlujne: jackieokay.com/2017/01/26/functors.html It może być jednak na to za późno, biorąc pod uwagę liczbę odpowiedzi, które uwzględniają jedynie znaczenie obiektu funkcji.
armb

2
Ta odpowiedź powinna brzmieć> 700 głosów poparcia. Jako ktoś, kto zna Haskell lepiej niż C ++, lingua w C ++ cały czas mnie zastanawiała.
mschmidt

Teoria kategorii i C ++? Czy to tajne konto SO Bartosza Milewskiego?
Mateen Ulhaq,

1
Pomocne może być podsumowanie praw funktora w standardowej notacji: fmap(id, x) = id(x)i fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq,

@mschmidt, podczas gdy funktor także to oznacza, C ++ przeciąża nazwę, aby znaczyła to samo co „obiekt funkcji”
Caleth

9

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.


3
Jeśli twój funktor wywoływał różne określone funkcje, a te inne funkcje różniły się liczbą akceptowanych parametrów, czy to oznacza, że ​​twój funktor zaakceptował zmienną liczbę argumentów do wysłania do tych innych funkcji?
johnbakers

4
czy możesz wyjaśnić powyższy scenariusz, cytując część kodu, jestem nowy w c ++ chcę zrozumieć tę koncepcję ..
sanjeev

3

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 .


To (przykład macierzy) jest zwykłym wykorzystaniem, operator()ale nie wykorzystaniem właściwości obiektu funkcji.
renardesque

3

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:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

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.


2

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:

void* method(void* something)

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 Threadklasy. Jeśli chcesz uruchomić metody z MyClassklasy, to co zrobiłem, przekształć te metody w Functorklasy pochodne.

Również Threadklasa ma tę metodę: static void* startThread(void* arg)
Wskaźnik do tej metody zostaną wykorzystane jako argument do rozmowy pthread_create(..). A to, co startThread(..)powinno otrzymać w arg, to void*rzutowane odwołanie do instancji w stercie dowolnej Functorklasy pochodnej, które zostanie rzutowane z powrotem Functor*po wykonaniu, a następnie nazwane jego run()metodą.


2

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:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

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.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Potrzebujemy również metody pomocniczej mem_fun3 dla powyższej klasy, aby pomóc w wywołaniu.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Teraz, aby powiązać parametry, musimy napisać funkcję spoiwa. Oto on:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

I funkcja pomocnicza do użycia klasy binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Teraz musimy użyć tego z klasą Command; użyj następującego typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Oto jak to nazywasz:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Uwaga: f3 (); wywoła metodę task1-> ThreeParameterTask (21,22,23) ;.

Pełny kontekst tego wzorca pod poniższym linkiem


2

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.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

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.


-10

„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 ;-)


Dlaczego opisujesz funktor jako „metodę bez nazwy”?
Anderson Green,

5
Funkcja bez nazwy nazywa się lambda.
Paul Fultz II,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.