Jaki jest sens wskaźników funkcji?

94

Mam problem ze zrozumieniem użyteczności wskaźników funkcji. Myślę, że w niektórych przypadkach może się to przydać (w końcu istnieją), ale nie przychodzi mi do głowy przypadek, w którym lepiej lub nieuniknione jest użycie wskaźnika funkcji.

Czy możesz podać przykład dobrego wykorzystania wskaźników funkcji (w C lub C ++)?

gram
źródło
1
Możesz znaleźć sporo dyskusji na temat wskaźników funkcji w tym pokrewnym pytaniu SO .
itsmatt
20
@itsmatt: Niezupełnie. „Jak działa telewizor?” to zupełnie inne pytanie niż „Co mam zrobić z telewizorem?”
sbi
6
W C ++ zamiast tego prawdopodobnie użyłbyś funktora ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ).
kennytm
11
W dawnych mrocznych czasach, kiedy C ++ był „kompilowany” do C, można było zobaczyć, jak implementowane są metody wirtualne - tak, ze wskaźnikami do funkcji.
sbk,
1
Bardzo istotne, gdy chcesz używać C ++ z zarządzanym C ++ lub C #, tj .: delegaci i wywołania zwrotne
Maher,

Odpowiedzi:

108

Większość przykładów sprowadza się do wywołań zwrotnych : wywołujesz funkcję f()przekazującą adres innej funkcji g()i f()wywołuje g()określone zadanie. Jeśli zamiast tego podasz f()adres h(), f()oddzwonię h().

Zasadniczo jest to sposób parametryzacji funkcji: pewna część jej zachowania nie jest zakodowana na stałe f(), ale w funkcji wywołania zwrotnego. Osoby wywołujące mogą f()zachowywać się inaczej, przekazując różne funkcje zwrotne. Klasyk pochodzi qsort()z biblioteki standardowej C, która przyjmuje kryterium sortowania jako wskaźnik do funkcji porównawczej.

W C ++ robi się to często za pomocą obiektów funkcji (zwanych również funktorami). Są to obiekty, które przeciążają operatora wywołania funkcji, więc można je wywoływać tak, jakby były funkcją. Przykład:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

Pomysł polega na tym, że w przeciwieństwie do wskaźnika funkcji obiekt funkcji może przenosić nie tylko algorytm, ale także dane:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

Inną zaletą jest to, że czasami łatwiej jest wywołać wbudowane wywołania do obiektów funkcji niż wywołania za pośrednictwem wskaźników funkcji. To jest powód, dla którego sortowanie w C ++ jest czasami szybsze niż sortowanie w C.

sbi
źródło
1
+1, zobacz także tę odpowiedź, aby zapoznać się z innym przykładem: stackoverflow.com/questions/1727824/…
ostry ząb
Zapomniałeś o funkcjach wirtualnych, zasadniczo są to również wskaźniki funkcji (w połączeniu ze strukturą danych, którą generuje kompilator). Co więcej, w czystym C możesz samodzielnie tworzyć te struktury, aby pisać kod zorientowany obiektowo, jak widać w warstwie VFS (i wielu innych miejscach) jądra Linuksa.
Florian
2
@krynr: Funkcje wirtualne są wskaźnikami funkcji tylko dla implementatorów kompilatora i jeśli musisz zapytać o to, do czego są przydatne, prawdopodobnie (miejmy nadzieję!) prawdopodobnie nie będziesz musiał zaimplementować mechanizmu funkcji wirtualnych kompilatora.
sbi
@sbi: Oczywiście masz rację. Uważam jednak, że pomaga to zrozumieć, co dzieje się wewnątrz abstrakcji. Również implementacja własnej tabeli vtable w C i pisanie kodu zorientowanego obiektowo daje naprawdę dobre doświadczenie w nauce.
Florian
Brownie punkty za udzielenie odpowiedzi na temat życia, wszechświata i wszystkiego, a także tego, o co prosił OP
nazwa uproszczona.
41

Cóż, generalnie używam ich (profesjonalnie) w tabelach skoków (zobacz także to pytanie StackOverflow ).

Tabele skoków są powszechnie (ale nie wyłącznie) używane w maszynach o skończonych stanach, aby sterować danymi. Zamiast zagnieżdżonego przełącznika / obudowy

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

możesz utworzyć tablicę wskaźników funkcji 2D i po prostu wywołać handleEvent[state][event]

Mawg mówi, że przywróć Monikę
źródło
24

Przykłady:

  1. Niestandardowe sortowanie / wyszukiwanie
  2. Różne wzorce (takie jak strategia, obserwator)
  3. Callback
Andrey
źródło
1
Stolik skokowy jest jednym z ważnych jego zastosowań.
Ashish
Gdyby istniały jakieś sprawdzone przykłady, dałoby to moje poparcie.
Donal Fellows
1
Strategia i obserwator są prawdopodobnie lepiej zaimplementowane przy użyciu funkcji wirtualnych, jeśli dostępny jest C ++. W przeciwnym razie +1.
Billy ONeal
myślę, że mądre wykorzystanie wskaźników funkcji może sprawić, że obserwator będzie bardziej zwarty i lekki
Andrey
@BillyONeal Tylko jeśli sztywno trzymasz się definicji GoF, z przeciekami Javaism. Chciałbym opisać std::sort„s compparametr jako strategia
Caleth
10

„Klasycznym” przykładem użyteczności wskaźników do funkcji jest qsort()funkcja biblioteki C , która implementuje szybkie sortowanie. Aby być uniwersalnym dla wszystkich i wszystkich struktur danych, które użytkownik może wymyślić, potrzeba kilku wskaźników pustej przestrzeni do sortowanych danych i wskaźnika do funkcji, która wie, jak porównać dwa elementy tych struktur danych. To pozwala nam stworzyć naszą wybraną funkcję dla zadania, a nawet pozwala na wybór funkcji porównawczej w czasie wykonywania, np. Do sortowania rosnąco lub malejąco.

Carl Smotricz
źródło
7

Zgadzam się ze wszystkimi powyższymi, plus ... Podczas dynamicznego ładowania biblioteki dll w czasie wykonywania, będziesz potrzebować wskaźników do funkcji, aby wywołać funkcje.

Bogaty
źródło
1
Robię to cały czas, aby wspierać system Windows XP i nadal używać dodatków systemu Windows 7. +1.
Billy ONeal
7

Mam zamiar pójść tutaj pod prąd.

W języku C wskaźniki funkcji są jedynym sposobem implementacji dostosowywania, ponieważ nie ma OO.

W C ++ możesz użyć wskaźników funkcji lub funktorów (obiektów funkcji) dla tego samego wyniku.

Funktory mają wiele zalet w porównaniu ze wskaźnikami funkcji surowej, ze względu na ich obiektowy charakter, a mianowicie:

  • Mogą powodować kilka przeciążeń pliku operator()
  • Mogą mieć stan / odniesienie do istniejących zmiennych
  • Można je zbudować na miejscu ( lambdai bind)

Osobiście wolę funktory od wskaźników do funkcji (pomimo standardowego kodu), głównie dlatego, że składnia wskaźników do funkcji może łatwo ulec owłosieniu (z samouczka dotyczącego wskaźników funkcji ):

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

Jedyny raz, kiedy widziałem wskaźniki funkcji używane tam, gdzie funktory nie mogły, był w Boost.Spirit. Całkowicie nadużyli składni, aby przekazać dowolną liczbę parametrów jako pojedynczy parametr szablonu.

 typedef SpecialClass<float(float,float)> class_type;

Ale ponieważ zmienne szablony i wyrażenia lambda są tuż za rogiem, nie jestem pewien, czy długo będziemy używać wskaźników funkcji w czystym kodzie C ++.

Matthieu M.
źródło
To, że nie widzisz wskaźników funkcji, nie oznacza, że ​​ich nie używasz. Za każdym razem (chyba że kompilator może ją zoptymalizować) wywołujesz funkcję wirtualną, używasz doładowania bindlub functionużywasz wskaźników do funkcji. To tak, jakby powiedzieć, że nie używamy wskaźników w C ++, ponieważ używamy inteligentnych wskaźników. Tak czy owak, czepiam się dzioba.
Florian
3
@krynr: Grzecznie się nie zgadzam. Liczy się to, co widzisz i wpisujesz , czyli składnia, której używasz. Nie powinno mieć dla ciebie znaczenia, jak to wszystko działa za kulisami: na tym polega abstrakcja .
Matthieu M.
5

W języku C klasycznym zastosowaniem jest funkcja qsort , w której czwartym parametrem jest wskaźnik do funkcji używanej do wykonywania porządkowania w ramach sortowania. W C ++ do tego rodzaju rzeczy można by używać funktorów (obiektów, które wyglądają jak funkcje).


źródło
2
@KennyTM: Wskazałem na jedyny inny przypadek tego w standardowej bibliotece C. Przytaczane przykłady należą do bibliotek stron trzecich.
Billy ONeal
5

Ostatnio użyłem wskaźników do funkcji, aby utworzyć warstwę abstrakcji.

Mam program napisany w czystym C, który działa na systemach wbudowanych. Obsługuje wiele wariantów sprzętu. W zależności od sprzętu, na którym pracuję, musi wywoływać różne wersje niektórych funkcji.

W czasie inicjalizacji program ustala, na jakim sprzęcie działa i zapełnia wskaźniki funkcji. Wszystkie procedury wyższego poziomu w programie po prostu wywołują funkcje, do których odwołują się wskaźniki. Mogę dodać obsługę nowych wariantów sprzętu bez dotykania procedur wyższego poziomu.

Kiedyś używałem instrukcji switch / case do wybierania odpowiednich wersji funkcji, ale stało się to niepraktyczne, ponieważ program zaczął obsługiwać coraz więcej wariantów sprzętowych. Wszędzie musiałem dodawać opisy przypadków.

Próbowałem również pośrednich warstw funkcyjnych, aby dowiedzieć się, której funkcji użyć, ale niewiele pomogły. Nadal musiałem aktualizować opisy przypadków w wielu miejscach, gdy dodawaliśmy nowy wariant. Dzięki wskaźnikom funkcji wystarczy zmienić funkcję inicjalizacyjną.

myron-semack
źródło
3

Podobnie jak Rich powiedział powyżej, bardzo często wskaźniki funkcji w systemie Windows odnoszą się do adresu, który przechowuje funkcje.

Podczas programowania C languagena platformie Windows w zasadzie ładujesz jakiś plik DLL do pamięci podstawowej (używając LoadLibrary) i aby korzystać z funkcji przechowywanych w DLL, musisz utworzyć wskaźniki funkcji i wskazać na te adresy (używając GetProcAddress).

Bibliografia:

Thomaz
źródło
2

Wskaźniki funkcji mogą być używane w C do tworzenia interfejsu do programowania. W zależności od konkretnej funkcji, która jest potrzebna w czasie wykonywania, do wskaźnika funkcji można przypisać inną implementację.

dafmetal
źródło
2

Moim głównym zastosowaniem były ODWOŁANIA: kiedy musisz zapisać informacje o funkcji, aby wywołać ją później .

Powiedz, że piszesz Bomberman. 5 sekund po tym, jak osoba upuści bombę, powinna wybuchnąć (wywołać explode()funkcję).

Teraz są na to dwa sposoby. Jednym ze sposobów jest „sondowanie” wszystkich bomb na ekranie, aby sprawdzić, czy są gotowe do wybuchu w głównej pętli.

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

Innym sposobem jest dołączenie wywołania zwrotnego do systemu zegarowego. Kiedy bomba jest podłożona, dodajesz wywołanie zwrotne, aby wywołać bomb.explode () w odpowiednim czasie .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

Tutaj callback.function()może być dowolna funkcja , ponieważ jest to wskaźnik funkcji.

bobobobo
źródło
Pytanie zostało oznaczone tagami [C] i [C ++], a nie żadnym innym znacznikiem języka. Dlatego udostępnianie fragmentów kodu w innym języku jest trochę nie na temat.
cmaster
2

Zastosowanie wskaźnika funkcji

Aby wywołać funkcję dynamicznie na podstawie danych wprowadzonych przez użytkownika. W tym przypadku tworząc mapę łańcucha i wskaźnika funkcji.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

W ten sposób użyliśmy wskaźnika funkcji w naszym rzeczywistym kodzie firmy. Możesz wpisać 'n' numer funkcji i wywołać je tą metodą.

WYNIK:

    Wprowadź swój wybór (1. Dodaj 2. Sub 3. Wiele): 1
    Wpisz 2 nie: 2 4
    6
    Wprowadź swój wybór (1. Dodaj 2. Sub 3. Wiele): 2
    Wpisz 2 nie: 10 3
    7
    Wprowadź swój wybór (1. Dodaj 2. Sub 3. Wiele): 3
    Wpisz 2 nie: 3 6
    18
Pankaj Kumar Boora
źródło
2

Z innej perspektywy, oprócz innych dobrych odpowiedzi tutaj:

W C używasz tylko wskaźników do funkcji, a nie (bezpośrednio) funkcji.

To znaczy, piszesz funkcje, ale nie możesz nimi manipulować . Nie ma reprezentacji funkcji jako takiej w czasie wykonywania, której można by użyć. Nie możesz nawet nazwać „funkcji”. Kiedy piszesz:

my_function(my_arg);

tak naprawdę mówisz „wykonaj wywołanie my_functionwskaźnika z określonym argumentem”. Wykonujesz wywołanie za pomocą wskaźnika funkcji. Ten zanik wskaźnika do funkcji oznacza, że ​​następujące polecenia są równoważne z poprzednim wywołaniem funkcji:

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

i tak dalej (dzięki @LuuVinhPhuc).

Więc już używasz wskaźników funkcji jako wartości . Oczywiście chciałbyś mieć zmienne dla tych wartości - i tutaj są wszystkie zastosowania innych metion: polimorfizm / dostosowywanie (jak w qsort), wywołania zwrotne, tabele skoku itp.

W C ++ sprawy są nieco bardziej skomplikowane, ponieważ mamy lambdy i obiekty z klasą, operator()a nawet z std::functionklasą, ale zasada jest nadal taka sama.

einpoklum
źródło
2
nawet bardziej interesujące, można wywołać funkcję jako (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... ponieważ funkcje rozkładowi do wskaźników funkcji
phuclv
1

W przypadku języków OO, do wykonywania wywołań polimorficznych za kulisami (myślę, że jest to również ważne dla C do pewnego momentu).

Ponadto są bardzo przydatne do wstrzykiwania innego zachowania do innej funkcji (foo) w czasie wykonywania. To sprawia, że ​​funkcja jest funkcją wyższego rzędu. Poza tym jest to elastyczność, która sprawia, że ​​kod foo jest bardziej czytelny, ponieważ pozwala wyciągnąć z niego dodatkową logikę „if-else”.

Umożliwia wiele innych przydatnych rzeczy w Pythonie, takich jak generatory, zamknięcia itp.

stdout
źródło
0

Używam często wskaźników funkcji do emulacji mikroprocesorów, które mają 1-bajtowe opkody. Tablica 256 wskaźników funkcji jest naturalnym sposobem implementacji tego.

TonyK
źródło
0

Jednym z zastosowań wskaźnika funkcji może być sytuacja, w której możemy nie chcieć modyfikować kodu, w którym funkcja jest wywoływana (co oznacza, że ​​wywołanie może być warunkowe iw innych warunkach musimy wykonać inny rodzaj przetwarzania). Tutaj wskaźniki funkcji są bardzo przydatne, ponieważ nie musimy modyfikować kodu w miejscu, w którym funkcja jest wywoływana. Po prostu wywołujemy funkcję za pomocą wskaźnika funkcji z odpowiednimi argumentami. Wskaźnik funkcji może wskazywać warunkowo różne funkcje. (Można to zrobić gdzieś podczas fazy inicjalizacji). Ponadto powyższy model jest bardzo pomocny, jeśli nie jesteśmy w stanie zmodyfikować kodu, w którym jest wywoływany (załóżmy, że jest to biblioteka API, której nie możemy modyfikować). API używa wskaźnika funkcji do wywoływania odpowiedniej funkcji zdefiniowanej przez użytkownika.

Sumit Trehan
źródło
0

Spróbuję podać tutaj nieco obszerną listę:

  • Wywołania zwrotne : dostosuj niektóre funkcje (biblioteki) za pomocą kodu dostarczonego przez użytkownika. Przykład Prime jest qsort(), ale jest również przydatny do obsługi zdarzeń (takich jak przycisk wywołujący wywołanie zwrotne po kliknięciu) lub niezbędny do rozpoczęcia wątku ( pthread_create()).

  • Polimorfizm : tabela vtable w klasie C ++ to nic innego jak tabela wskaźników funkcji. Program w C może również udostępnić tabelę vtable dla niektórych swoich obiektów:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }

    Konstruktor programu Derivedustawiłby następnie swoją vtablezmienną składową na obiekt globalny z implementacjami destructi klasy pochodnej frobnicate, a kod, który musiałby zniszczyć a struct Base*, po prostu base->vtable->destruct(base)wywołałby poprawną wersję destruktora, niezależnie od tego, która klasa pochodna basefaktycznie wskazuje na .

    Bez wskaźników funkcyjnych polimorfizm musiałby zostać zakodowany za pomocą całej armii konstrukcji przełączających, takich jak

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }

    To dość szybko staje się nieporęczne.

  • Kod ładowany dynamicznie : gdy program ładuje moduł do pamięci i próbuje wywołać swój kod, musi przejść przez wskaźnik funkcji.

Wszystkie zastosowania wskaźników funkcji, które widziałem, mieszczą się w jednej z tych trzech szerokich klas.

cmaster - przywróć monikę
źródło