Apa gunanya pointer fungsi?


94

Saya kesulitan melihat kegunaan pointer fungsi. Saya kira ini mungkin berguna dalam beberapa kasus (bagaimanapun juga mereka ada), tetapi saya tidak dapat memikirkan kasus di mana lebih baik atau tidak dapat dihindari untuk menggunakan penunjuk fungsi.

Bisakah Anda memberikan beberapa contoh penggunaan yang baik dari pointer fungsi (dalam C atau C ++)?


1
Anda dapat menemukan sedikit diskusi tentang pointer fungsi dalam pertanyaan SO terkait ini .
itsmatt

20
@itsmatt: Tidak juga. "Bagaimana cara kerja TV?" adalah pertanyaan yang agak berbeda dari "Apa yang harus saya lakukan dengan TV?"
sbi

6
Di C ++ Anda mungkin akan menggunakan functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) sebagai gantinya.
kennytm

11
Di masa lalu yang gelap ketika C ++ "dikompilasi" ke C, Anda benar-benar dapat melihat bagaimana metode virtual diimplementasikan - ya, dengan pointer fungsi.
sbk

1
Sangat penting ketika Anda ingin menggunakan C ++ dengan C ++ atau C # terkelola, yaitu: delegasi dan callback
Maher

Jawaban:


108

Sebagian besar contoh bermuara pada callback : Anda memanggil fungsi yang f()meneruskan alamat fungsi lain g(), dan f()memanggil g()beberapa tugas tertentu. Jika Anda mengirimkan f()alamat h()sebagai gantinya, maka f()akan menelepon kembali h()sebagai gantinya.

Pada dasarnya, ini adalah cara untuk melakukan parametrize pada suatu fungsi: Beberapa bagian dari perilakunya tidak di-hardcode f(), tetapi ke dalam fungsi callback. Penelepon dapat f()berperilaku berbeda dengan meneruskan fungsi panggilan balik yang berbeda. Klasik berasal qsort()dari pustaka standar C yang menggunakan kriteria pengurutan sebagai penunjuk ke fungsi perbandingan.

Dalam C ++, ini sering dilakukan dengan menggunakan objek fungsi (juga disebut functors). Ini adalah objek yang membebani operator pemanggil fungsi, sehingga Anda dapat memanggilnya seolah-olah mereka adalah sebuah fungsi. Contoh:

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

functor f;
f(42);

Ide di balik ini adalah, tidak seperti penunjuk fungsi, objek fungsi tidak hanya dapat membawa algoritme, tetapi juga data:

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

Keuntungan lainnya adalah terkadang lebih mudah untuk panggilan sebaris ke objek fungsi daripada panggilan melalui pointer fungsi. Inilah alasan mengapa pengurutan di C ++ terkadang lebih cepat daripada pengurutan di C.


1
+1, lihat juga jawaban ini untuk contoh lain: stackoverflow.com/questions/1727824/…
sharptooth

Anda lupa fungsi virtual, pada dasarnya mereka adalah penunjuk fungsi juga (digabungkan dengan struktur data yang dihasilkan kompilator). Lebih lanjut, dalam C murni Anda dapat membuat struktur ini sendiri untuk menulis kode berorientasi objek seperti yang terlihat di lapisan VFS (dan di banyak tempat lain) dari kernel Linux.
Florian

2
@krynr: Fungsi virtual adalah penunjuk fungsi hanya untuk pelaksana kompiler, dan jika Anda harus menanyakan kegunaannya, Anda mungkin (mudah-mudahan!) mungkin tidak perlu mengimplementasikan mekanisme fungsi virtual kompiler.
sbi

@sbi: Anda benar tentu saja. Namun, saya pikir akan membantu untuk memahami apa yang terjadi di dalam abstraksi. Selain itu, mengimplementasikan vtable Anda sendiri di C dan menulis kode berorientasi objek adalah pengalaman belajar yang sangat bagus.
Florian

poin brownies untuk memberikan jawaban atas kehidupan, alam semesta dan segala sesuatu beserta apa yang diminta oleh OP
simplename

41

Yah, saya biasanya menggunakannya (secara profesional) dalam tabel lompat (lihat juga pertanyaan StackOverflow ini ).

Tabel lompat biasanya (tetapi tidak secara eksklusif) digunakan dalam mesin keadaan terbatas untuk membuatnya digerakkan oleh data. Alih-alih switch / case bersarang

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

Anda dapat membuat array 2d dari fungsi pointer dan hanya memanggil handleEvent[state][event]


24

Contoh:

  1. Penyortiran / pencarian kustom
  2. Pola berbeda (seperti Strategi, Pengamat)
  3. Panggilan balik

1
Jump table adalah salah satu kegunaan penting darinya.
Ashish

Jika ada beberapa contoh yang berhasil, ini akan mendapatkan suara positif saya.
Donal Fellows

1
Strategi dan pengamat mungkin lebih baik diimplementasikan menggunakan fungsi virtual, jika C ++ tersedia. Jika tidak +1.
Billy ONeal

Saya pikir penggunaan fungsi pointer yang bijak dapat membuat pengamat lebih kompak dan ringan
Andrey

@BillyONeal Hanya jika Anda secara kaku berpegang pada definisi GoF, dengan Javaisme yang bocor. Saya akan menggambarkan std::sort's compparameter sebagai Strategi
Caleth

10

Contoh "klasik" untuk kegunaan penunjuk fungsi adalah qsort()fungsi pustaka C , yang mengimplementasikan Quick Sort. Agar menjadi universal untuk setiap dan semua struktur data yang mungkin dibuat oleh pengguna, diperlukan beberapa penunjuk kosong ke data yang dapat diurutkan dan penunjuk ke fungsi yang mengetahui cara membandingkan dua elemen dari struktur data ini. Hal ini memungkinkan kita untuk membuat fungsi pilihan untuk pekerjaan tersebut, dan bahkan memungkinkan untuk memilih fungsi perbandingan pada waktu berjalan, misalnya untuk mengurutkan naik atau turun.


7

Setuju dengan semua hal di atas, ditambah .... Saat Anda memuat dll secara dinamis saat runtime, Anda memerlukan pointer fungsi untuk memanggil fungsi.


1
Saya melakukan ini sepanjang waktu untuk mendukung Windows XP dan masih menggunakan perangkat Windows 7. +1.
Billy ONeal

7

Saya akan melawan arus di sini.

Di C, pointer fungsi adalah satu-satunya cara untuk mengimplementasikan kustomisasi, karena tidak ada OO.

Dalam C ++, Anda dapat menggunakan salah satu penunjuk fungsi atau functor (objek fungsi) untuk hasil yang sama.

Functors memiliki sejumlah keunggulan dibandingkan raw function pointer, karena sifat objeknya, terutama:

  • Mereka mungkin menyajikan beberapa kelebihan file operator()
  • Mereka dapat memiliki status / referensi ke variabel yang ada
  • Mereka dapat dibangun di tempat ( lambdadan bind)

Saya pribadi lebih suka functors ke pointer fungsi (terlepas dari kode boilerplate), sebagian besar karena sintaks untuk pointer fungsi dapat dengan mudah menjadi berbulu (dari Tutorial Function Pointer ):

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

Satu-satunya saat saya pernah melihat pointer fungsi digunakan di mana functor tidak bisa berada di Boost.Spirit. Mereka benar-benar menyalahgunakan sintaks untuk meneruskan sejumlah parameter yang berubah-ubah sebagai satu parameter template.

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

Tapi karena template variadic dan lambda sudah dekat, saya tidak yakin kita akan menggunakan pointer fungsi dalam kode C ++ murni untuk waktu yang lama.


Hanya karena Anda tidak melihat pointer fungsi Anda tidak berarti Anda tidak menggunakannya. Setiap kali (kecuali kompiler dapat mengoptimalkannya) Anda memanggil fungsi virtual, menggunakan boost bindatau functionAnda menggunakan pointer fungsi. Ini seperti mengatakan kami tidak menggunakan pointer di C ++ karena kami menggunakan pointer pintar. Bagaimanapun, saya rewel.
Florian

3
@ Krynr: Saya akan dengan sopan tidak setuju. Yang penting adalah apa yang Anda lihat dan ketik , itulah sintaks yang Anda gunakan. Tidak masalah bagi Anda bagaimana semuanya bekerja di belakang layar: inilah yang dimaksud dengan abstraksi .
Matthieu M.

5

Di C, penggunaan klasik adalah fungsi qsort , di mana parameter keempat adalah penunjuk ke fungsi yang digunakan untuk melakukan pengurutan dalam sort. Dalam C ++, seseorang akan cenderung menggunakan functors (objek yang terlihat seperti fungsi) untuk hal semacam ini.


2
@KennyTM: Saya menunjukkan satu-satunya contoh lain dari ini di perpustakaan standar C. Contoh yang Anda kutip adalah bagian dari perpustakaan pihak ketiga.
Billy ONeal

5

Saya menggunakan pointer fungsi baru-baru ini untuk membuat lapisan abstraksi.

Saya memiliki program yang ditulis dalam C murni yang berjalan pada sistem tertanam. Ini mendukung beberapa varian perangkat keras. Bergantung pada perangkat keras yang saya gunakan, itu perlu memanggil versi berbeda dari beberapa fungsi.

Pada waktu inisialisasi, program mencari tahu perangkat keras apa yang dijalankannya dan mengisi penunjuk fungsi. Semua rutinitas tingkat tinggi dalam program hanya memanggil fungsi yang dirujuk oleh pointer. Saya dapat menambahkan dukungan untuk varian perangkat keras baru tanpa menyentuh rutinitas tingkat yang lebih tinggi.

Saya dulu menggunakan pernyataan switch / case untuk memilih versi fungsi yang tepat, tetapi ini menjadi tidak praktis karena program tersebut berkembang untuk mendukung lebih banyak varian perangkat keras. Saya harus menambahkan pernyataan kasus di semua tempat.

Saya juga mencoba lapisan fungsi menengah untuk mencari tahu fungsi mana yang akan digunakan, tetapi mereka tidak banyak membantu. Saya masih harus memperbarui pernyataan kasus di banyak tempat setiap kali kami menambahkan varian baru. Dengan fungsi pointer, saya hanya perlu mengubah fungsi inisialisasi.


3

Seperti yang dikatakan Rich di atas, biasanya pointer fungsi di Windows merujuk ke beberapa alamat yang menyimpan fungsi.

Saat Anda memprogram di C languageplatform Windows, pada dasarnya Anda memuat beberapa file DLL di memori utama (menggunakan LoadLibrary) dan untuk menggunakan fungsi yang disimpan di DLL, Anda perlu membuat penunjuk fungsi dan menunjuk ke alamat ini (menggunakan GetProcAddress).

Referensi:


2

Pointer fungsi dapat digunakan di C untuk membuat antarmuka yang akan diprogram. Bergantung pada fungsionalitas spesifik yang diperlukan saat runtime, implementasi berbeda dapat ditetapkan ke penunjuk fungsi.


2

Penggunaan utama saya dari mereka adalah PANGGILAN: ketika Anda perlu menyimpan informasi tentang suatu fungsi untuk dipanggil nanti .

Katakanlah Anda sedang menulis Bomberman. 5 detik setelah orang tersebut menjatuhkan bom, bom tersebut akan meledak (panggil explode()fungsinya).

Sekarang ada 2 cara untuk melakukannya. Salah satu caranya adalah dengan "menyelidiki" semua bom di layar untuk melihat apakah bom tersebut siap meledak di loop utama.

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

Cara lain adalah dengan memasang panggilan balik ke sistem jam Anda. Saat bom ditanam, Anda menambahkan callback untuk memanggil bomb.explode () jika waktunya tepat .

// 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()

Di sini callback.function()bisa ada fungsi apa saja , karena ini adalah fungsi penunjuk.


Pertanyaan telah diberi tag dengan [C] dan [C ++], bukan tag bahasa lainnya. Karenanya, memberikan potongan kode dalam bahasa lain agak di luar topik.
cmaster - memulihkan monica

2

Penggunaan penunjuk fungsi

Untuk memanggil fungsi secara dinamis berdasarkan masukan pengguna. Dengan membuat peta string dan fungsi pointer dalam kasus ini.

#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;
}

Dengan cara ini kami telah menggunakan penunjuk fungsi dalam kode perusahaan kami yang sebenarnya. Anda dapat menulis nomor 'n' dari fungsi dan memanggilnya menggunakan metode ini.

KELUARAN:

    Masukkan pilihan Anda (1. Tambahkan 2. Sub 3. Multi): 1
    Masukkan 2 no: 2 4
    6
    Masukkan pilihan Anda (1. Tambahkan 2. Sub 3. Multi): 2
    Masukkan 2 no: 10 3
    7
    Masukkan pilihan Anda (1. Tambahkan 2. Sub 3. Multi): 3
    Masukkan 2 no: 3 6
    18

2

Perspektif yang berbeda, selain jawaban bagus lainnya di sini:

Di C, Anda hanya menggunakan fungsi pointer, bukan fungsi (secara langsung).

Maksud saya, Anda menulis fungsi, tetapi Anda tidak dapat memanipulasi fungsi. Tidak ada representasi run-time dari suatu fungsi yang dapat Anda gunakan. Anda bahkan tidak bisa menyebut "fungsi". Saat Anda menulis:

my_function(my_arg);

apa yang sebenarnya Anda katakan adalah "melakukan panggilan ke my_functionpointer dengan argumen yang ditentukan". Anda melakukan panggilan melalui penunjuk fungsi. Peluruhan ini menjadi penunjuk fungsi berarti bahwa perintah berikut ini setara dengan pemanggilan fungsi sebelumnya:

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

dan seterusnya (terima kasih @LuuVinhPhuc).

Jadi, Anda sudah menggunakan pointer fungsi sebagai nilai . Jelas Anda ingin memiliki variabel untuk nilai-nilai itu - dan di sinilah semua penggunaan metion lain masuk: Polimorfisme / kustomisasi (seperti di qsort), callback, tabel lompat dll.

Dalam C ++ hal-hal menjadi sedikit lebih rumit, karena kita memiliki lambda, dan objek dengan operator(), dan bahkan sebuah std::functionkelas, tetapi prinsipnya sebagian besar masih sama.


2
bahkan lebih menarik, Anda dapat memanggil fungsi sebagai (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... karena fungsi meluruh sampai pointer fungsi
phuclv

1

Untuk bahasa OO, untuk melakukan panggilan polimorfik di belakang layar (ini juga berlaku untuk C hingga titik tertentu, saya kira).

Selain itu, mereka sangat berguna untuk memasukkan perilaku yang berbeda ke fungsi lain (foo) saat runtime. Itu membuat fungsi menjadi fungsi tingkat tinggi. Selain fleksibilitasnya, yang membuat kode foo lebih mudah dibaca karena memungkinkan Anda menarik logika ekstra "if-else" darinya.

Ini memungkinkan banyak hal berguna lainnya dengan Python seperti generator, closure dll.


0

Saya menggunakan pointer fungsi secara ekstensif, untuk meniru mikroprosesor yang memiliki opcode 1-byte. Sebuah array dari 256 pointer fungsi adalah cara alami untuk mengimplementasikannya.


0

Satu penggunaan pointer fungsi bisa jadi di mana kita mungkin tidak ingin mengubah kode di mana fungsi dipanggil (artinya dengan demikian panggilan mungkin bersyarat dan dalam kondisi yang berbeda, kita perlu melakukan jenis pemrosesan yang berbeda). Di sini pointer fungsi sangat berguna, karena kita tidak perlu memodifikasi kode di tempat fungsi dipanggil. Kami cukup memanggil fungsi menggunakan penunjuk fungsi dengan argumen yang sesuai. Penunjuk fungsi dapat dibuat untuk menunjukkan fungsi yang berbeda secara kondisional. (Ini dapat dilakukan di suatu tempat selama fase inisialisasi). Selain itu, model di atas sangat membantu, jika kita tidak dalam posisi untuk mengubah kode di mana ia dipanggil (misalkan itu adalah API perpustakaan yang tidak dapat kita modifikasi). API menggunakan penunjuk fungsi untuk memanggil fungsi yang ditentukan pengguna yang sesuai.


0

Saya akan mencoba memberikan daftar yang agak lengkap di sini:

  • Callbacks : Sesuaikan beberapa fungsionalitas (pustaka) dengan kode yang diberikan pengguna. Contoh utamanya adalah qsort(), tetapi juga berguna untuk menangani peristiwa (seperti tombol yang memanggil callback saat diklik), atau diperlukan untuk memulai thread ( pthread_create()).

  • Polimorfisme : vtabel di kelas C ++ tidak lain adalah tabel penunjuk fungsi. Dan program C juga dapat memilih untuk menyediakan vtable untuk beberapa objeknya:

    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 dari Derivedkemudian akan menetapkan vtablevariabel anggotanya ke objek global dengan implementasi kelas turunan dari destructdan frobnicate, dan kode yang diperlukan untuk menghancurkan sebuah struct Base*panggilan akan cukup base->vtable->destruct(base), yang akan memanggil versi destruktor yang benar, terlepas dari kelas turunan yang basesebenarnya menunjuk ke .

    Tanpa pointer fungsi, polimorfisme perlu dikodekan dengan sekumpulan konstruksi sakelar seperti

    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;
    }

    Ini menjadi agak berat dengan cepat.

  • Kode yang dimuat secara dinamis : Ketika sebuah program memuat modul ke dalam memori dan mencoba memanggil kodenya, program tersebut harus melalui penunjuk fungsi.

Semua penggunaan pointer fungsi yang pernah saya lihat termasuk dalam salah satu dari tiga kelas besar ini.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.