Di C ++, kapan dan bagaimana Anda menggunakan fungsi panggilan balik?
EDIT:
Saya ingin melihat contoh sederhana untuk menulis fungsi panggilan balik.
Di C ++, kapan dan bagaimana Anda menggunakan fungsi panggilan balik?
EDIT:
Saya ingin melihat contoh sederhana untuk menulis fungsi panggilan balik.
Jawaban:
Catatan: Sebagian besar jawaban mencakup pointer fungsi yang merupakan salah satu kemungkinan untuk mencapai logika "panggilan balik" dalam C ++, tetapi pada hari ini bukan yang paling menguntungkan yang saya pikir.
Apa itu callback (?) Dan mengapa menggunakannya (!)
Callback adalah callable (lihat lebih jauh ke bawah) yang diterima oleh kelas atau fungsi, yang digunakan untuk menyesuaikan logika saat ini tergantung pada callback itu.
Salah satu alasan untuk menggunakan callback adalah untuk menulis kode generik yang independen dari logika pada fungsi yang dipanggil dan dapat digunakan kembali dengan callback yang berbeda.
Banyak fungsi pustaka algoritma standar
<algorithm>
menggunakan panggilan balik. Misalnyafor_each
algoritme menerapkan panggilan balik unary ke setiap item dalam rentang iterator:yang dapat digunakan untuk kenaikan pertama dan kemudian mencetak vektor dengan melewatkan perangkat yang sesuai misalnya:
yang mencetak
Aplikasi lain dari callback adalah pemberitahuan penelepon dari peristiwa tertentu yang memungkinkan sejumlah fleksibilitas waktu statis / kompilasi.
Secara pribadi, saya menggunakan perpustakaan pengoptimalan lokal yang menggunakan dua panggilan balik berbeda:
Dengan demikian, perancang perpustakaan tidak bertanggung jawab untuk memutuskan apa yang terjadi dengan informasi yang diberikan kepada programmer melalui callback notifikasi dan dia tidak perlu khawatir tentang bagaimana sebenarnya menentukan nilai fungsi karena mereka disediakan oleh callback logika. Memperbaiki hal-hal itu adalah tugas karena pengguna perpustakaan dan menjaga perpustakaan langsing dan lebih umum.
Selanjutnya, panggilan balik dapat mengaktifkan perilaku runtime dinamis.
Bayangkan beberapa jenis kelas mesin gim yang memiliki fungsi yang dipecat, setiap kali pengguna menekan tombol di keyboard-nya dan serangkaian fungsi yang mengontrol perilaku gim Anda. Dengan panggilan balik Anda dapat (kembali) memutuskan pada saat runtime tindakan mana yang akan diambil.
Di sini fungsi
key_pressed
menggunakan panggilan balik yang disimpanactions
untuk mendapatkan perilaku yang diinginkan ketika tombol tertentu ditekan. Jika pemain memilih untuk mengubah tombol untuk melompat, mesin dapat memanggildan dengan demikian mengubah perilaku panggilan ke
key_pressed
(panggilan manaplayer_jump
) setelah tombol ini ditekan pada saat ingame berikutnya.Apa itu callable di C ++ (11)?
Lihat konsep C ++: Callable on cppreference untuk deskripsi yang lebih formal.
Fungsionalitas panggilan balik dapat diwujudkan dalam beberapa cara dalam C ++ (11) karena beberapa hal yang berbeda ternyata dapat dipanggil * :
std::function
bendaoperator()
)* Catatan: Pointer ke anggota data juga dapat dipanggil tetapi tidak ada fungsi yang dipanggil sama sekali.
Beberapa cara penting untuk menulis panggilan balik secara terperinci
Catatan: Pada C ++ 17, panggilan seperti
f(...)
dapat ditulis sebagaistd::invoke(f, ...)
yang juga menangani pointer ke case anggota.1. Pointer fungsi
Pointer fungsi adalah yang paling sederhana (dalam hal generalitas; dalam hal keterbacaan bisa dibilang yang terburuk) jenis callback dapat memiliki.
Mari kita fungsi sederhana
foo
:1.1 Menulis fungsi pointer / notasi jenis
Sebuah tipe fungsi pointer memiliki notasi
di mana jenis penunjuk fungsi bernama akan terlihat seperti
The
using
deklarasi memberi kita pilihan untuk membuat hal-hal sedikit lebih mudah dibaca, karenatypedef
untukf_int_t
juga dapat ditulis sebagai:Di mana (setidaknya bagi saya) lebih jelas bahwa itu
f_int_t
adalah tipe baru alias dan pengenalan tipe fungsi pointer juga lebih mudahDan deklarasi fungsi yang menggunakan callback dari tipe pointer fungsi adalah:
1.2 Notasi panggilan balik
Notasi panggilan mengikuti sintaks panggilan fungsi sederhana:
1.3 Callback menggunakan notasi dan tipe yang kompatibel
Fungsi callback mengambil pointer fungsi dapat disebut menggunakan pointer fungsi.
Menggunakan fungsi yang mengambil callback fungsi pointer agak sederhana:
1.4 Contoh
Fungsi yang ditulis yang tidak bergantung pada cara kerja callback:
di mana panggilan balik mungkin bisa
digunakan seperti
2. Pointer ke fungsi anggota
Sebuah pointer ke fungsi anggota (dari beberapa kelas
C
) adalah tipe khusus dari (dan bahkan lebih kompleks) pointer fungsi yang membutuhkan objek tipeC
untuk beroperasi.2.1 Menulis pointer ke notasi fungsi / tipe anggota
Sebuah pointer ke tipe fungsi anggota untuk beberapa kelas
T
memiliki notasidi mana penunjuk bernama untuk fungsi anggota akan -di analogi dengan fungsi penunjuk- terlihat seperti ini:
Contoh: Mendeklarasikan fungsi yang mengambil pointer ke fungsi panggilan balik anggota sebagai salah satu argumennya:
2.2 Notasi panggilan balik
Fungsi pointer ke anggota
C
dapat dipanggil, sehubungan dengan objek tipeC
dengan menggunakan operasi akses anggota pada pointer dereferenced. Catatan: Diperlukan Parenthesis!Catatan: Jika sebuah pointer ke
C
tersedia, sintaksinya setara (di mana pointer jugaC
harus didereferensi):2.3 Notasi penggunaan panggilan balik dan jenis yang kompatibel
Fungsi callback mengambil pointer fungsi anggota kelas
T
dapat dipanggil menggunakan pointer fungsi anggota kelasT
.Menggunakan fungsi yang mengambil pointer ke fungsi anggota callback adalah -di analogi dengan fungsi pointer- cukup sederhana juga:
3.
std::function
objek (header<functional>
)The
std::function
kelas adalah fungsi wrapper polimorfik untuk menyimpan, menyalin atau Invoke callables.3.1 Menulis
std::function
notasi objek / tipeJenis
std::function
objek menyimpan callable terlihat seperti:3.2 Notasi panggilan balik
Kelas
std::function
telahoperator()
ditentukan yang dapat digunakan untuk menjalankan targetnya.3.3 Notasi penggunaan panggilan balik dan jenis yang kompatibel
The
std::function
callback lebih generik dari pointer fungsi atau pointer ke fungsi anggota karena jenis yang berbeda dapat berlalu dan secara implisit diubah menjadistd::function
objek.3.3.1 Pointer fungsi dan pointer ke fungsi anggota
Pointer fungsi
atau pointer ke fungsi anggota
dapat digunakan.
3.3.2 Ekspresi Lambda
Penutupan tanpa ekspresi dari ekspresi lambda dapat disimpan di
std::function
objek:3.3.3
std::bind
ekspresiHasil dari
std::bind
ekspresi dapat dilewati. Misalnya dengan mengikat parameter ke panggilan fungsi pointer:Di mana juga objek dapat diikat sebagai objek untuk pemanggilan pointer ke fungsi anggota:
3.3.4 Objek fungsi
Objek kelas yang memiliki
operator()
kelebihan yang tepat dapat disimpan di dalamstd::function
objek juga.3.4 Contoh
Mengubah contoh penunjuk fungsi untuk digunakan
std::function
memberi jauh lebih banyak utilitas untuk fungsi itu karena (lihat 3.3) kami memiliki lebih banyak kemungkinan untuk menggunakannya:
4. Jenis panggilan balik templated
Menggunakan templat, kode yang memanggil panggilan balik bisa lebih umum daripada menggunakan
std::function
objek.Perhatikan bahwa templat adalah fitur waktu kompilasi dan merupakan alat desain untuk polimorfisme waktu kompilasi. Jika perilaku dinamis runtime ingin dicapai melalui callback, template akan membantu tetapi mereka tidak akan menginduksi dinamika runtime.
4.1 Menulis (mengetik notasi) dan memanggil panggilan balik templated
Generalisasi yaitu
std_ftransform_every_int
kode dari atas lebih jauh dapat dicapai dengan menggunakan templat:dengan sintaks yang lebih umum (dan juga termudah) untuk tipe panggilan balik menjadi argumen templated sederhana yang akan dideduksi:
Catatan: Output yang disertakan mencetak nama jenis yang disimpulkan untuk jenis templated
F
. Implementasitype_name
diberikan pada akhir posting ini.Implementasi paling umum untuk transformasi unary dari jangkauan adalah bagian dari perpustakaan standar, yaitu
std::transform
, yang juga disesuaikan dengan jenis yang diulang.4.2 Contoh menggunakan callback templated dan tipe yang kompatibel
Tipe yang kompatibel untuk
std::function
metode callback templatedstdf_transform_every_int_templ
identik dengan tipe yang disebutkan di atas (lihat 3.4).Namun, menggunakan versi templated, tanda tangan dari panggilan balik yang digunakan dapat berubah sedikit:
Catatan:
std_ftransform_every_int
(versi non templated; lihat di atas) berfungsi denganfoo
tetapi tidak menggunakanmuh
.Parameter templated polos dari
transform_every_int_templ
dapat setiap jenis yang bisa dipanggil.Kode di atas mencetak:
type_name
implementasi yang digunakan di atassumber
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, ini salah cetak kan?foo
harus menjadi penunjuk untuk ini bekerja AFAIK.[conv.func]
dari standar C ++ 11 mengatakan: "Nilai fungsi tipe T dapat dikonversi ke nilai awal tipe" pointer to T. " Hasilnya adalah penunjuk ke fungsi. "Ini adalah konversi standar dan karenanya terjadi secara implisit. Orang bisa (tentu saja) menggunakan pointer fungsi di sini.Ada juga cara C untuk melakukan panggilan balik: pointer fungsi
Sekarang jika Anda ingin meneruskan metode kelas sebagai panggilan balik, deklarasi ke pointer fungsi tersebut memiliki deklarasi yang lebih kompleks, contoh:
sumber
typedef
tipe panggilan balik? Apakah itu mungkin?typedef
hanyalah gula sintaksis agar lebih mudah dibaca. Tanpatypedef
, definisi DoWorkObject untuk fungsi pointer akan menjadi:void DoWorkObject(int (*callback)(float))
. Untuk petunjuk anggota adalah:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers memberikan contoh yang bagus:
Saya pikir contoh mengatakan semuanya.
std::function<>
adalah cara "modern" untuk menulis panggilan balik C ++.sumber
Sebuah fungsi Callback adalah metode yang dilewatkan ke rutinitas, dan disebut di beberapa titik oleh rutinitas yang itu berlalu.
Ini sangat berguna untuk membuat perangkat lunak yang dapat digunakan kembali. Misalnya, banyak API sistem operasi (seperti Windows API) banyak menggunakan panggilan balik.
Misalnya, jika Anda ingin bekerja dengan file dalam folder - Anda dapat memanggil fungsi API, dengan rutinitas Anda sendiri, dan rutin Anda dijalankan sekali per file dalam folder yang ditentukan. Ini memungkinkan API menjadi sangat fleksibel.
sumber
Jawaban yang diterima sangat bermanfaat dan cukup komprehensif. Namun, OP menyatakan
Jadi di sini Anda mulai, dari C ++ 11 yang Anda miliki
std::function
sehingga tidak perlu pointer fungsi dan hal-hal serupa:Contoh ini benar-benar nyata, karena Anda ingin memanggil fungsi
print_hashes
dengan implementasi fungsi hash yang berbeda, untuk tujuan ini saya berikan yang sederhana. Ini menerima string, mengembalikan int (nilai hash dari string yang disediakan), dan semua yang perlu Anda ingat dari bagian sintaks adalahstd::function<int (const std::string&)>
yang menggambarkan fungsi seperti argumen input dari fungsi yang akan memanggilnya.sumber
Tidak ada konsep eksplisit fungsi panggilan balik di C ++. Mekanisme panggilan balik sering diterapkan melalui pointer fungsi, objek functor, atau objek panggilan balik. Programmer harus secara eksplisit mendesain dan mengimplementasikan fungsi callback.
Edit berdasarkan umpan balik:
Terlepas dari umpan balik negatif yang diterima jawaban ini, itu tidak salah. Saya akan mencoba melakukan pekerjaan yang lebih baik untuk menjelaskan dari mana saya berasal.
C dan C ++ memiliki semua yang Anda butuhkan untuk mengimplementasikan fungsi panggilan balik. Cara paling umum dan sepele untuk mengimplementasikan fungsi callback adalah dengan melewatkan pointer fungsi sebagai argumen fungsi.
Namun, fungsi panggilan balik dan fungsi pointer tidak sama. Pointer fungsi adalah mekanisme bahasa, sedangkan fungsi callback adalah konsep semantik. Pointer fungsi bukan satu-satunya cara untuk mengimplementasikan fungsi callback - Anda juga dapat menggunakan functors dan bahkan berbagai fungsi virtual kebun. Apa yang membuat suatu fungsi memanggil panggilan balik bukanlah mekanisme yang digunakan untuk mengidentifikasi dan memanggil fungsi, tetapi konteks dan semantik dari panggilan tersebut. Mengatakan sesuatu adalah fungsi callback menyiratkan pemisahan yang lebih besar dari normal antara fungsi pemanggilan dan fungsi spesifik yang dipanggil, penggabungan konseptual yang lebih longgar antara pemanggil dan callee, dengan pemanggil memiliki kontrol eksplisit atas apa yang dipanggil.
Sebagai contoh, dokumentasi .NET untuk IFormatProvider mengatakan bahwa "GetFormat adalah metode panggilan balik" , meskipun itu hanya metode antarmuka run-of-the-mill. Saya tidak berpikir ada orang yang berpendapat bahwa semua panggilan metode virtual adalah fungsi panggilan balik. Apa yang menjadikan GetFormat sebagai metode panggilan balik bukanlah mekanisme bagaimana hal itu dilewatkan atau dipanggil, tetapi semantik penelepon memilih metode GetFormat objek mana yang akan dipanggil.
Beberapa bahasa menyertakan fitur dengan semantik panggilan balik eksplisit, biasanya terkait dengan acara dan penanganan acara. Misalnya, C # memiliki tipe acara dengan sintaks dan semantik yang dirancang secara eksplisit di sekitar konsep panggilan balik. Visual Basic memiliki klausa Handles -nya , yang secara eksplisit mendeklarasikan metode menjadi fungsi callback sambil mengabstraksi konsep delegasi atau pointer fungsi. Dalam kasus ini, konsep semantik panggilan balik diintegrasikan ke dalam bahasa itu sendiri.
C dan C ++, di sisi lain, tidak menanamkan konsep semantik fungsi callback hampir secara eksplisit. Mekanismenya ada, semantik terintegrasi tidak. Anda dapat mengimplementasikan fungsi panggilan balik dengan baik, tetapi untuk mendapatkan sesuatu yang lebih canggih yang mencakup semantik panggilan balik eksplisit Anda harus membangunnya di atas apa yang disediakan C ++, seperti apa yang dilakukan Qt dengan Sinyal dan Slot mereka .
Singkatnya, C ++ memiliki apa yang Anda butuhkan untuk mengimplementasikan panggilan balik, seringkali cukup mudah dan sepele menggunakan pointer fungsi. Apa yang tidak dimilikinya adalah kata kunci dan fitur yang semantiknya khusus untuk panggilan balik, seperti kenaikan , buang , Pegangan , acara + = , dll. Jika Anda berasal dari bahasa dengan jenis elemen tersebut, panggilan balik asli mendukung dalam C ++ akan merasa dikebiri.
sumber
Fungsi panggilan balik adalah bagian dari standar C, oleh karena itu juga merupakan bagian dari C ++. Tetapi jika Anda bekerja dengan C ++, saya sarankan Anda menggunakan pola pengamat saja: http://en.wikipedia.org/wiki/Observer_pattern
sumber
Lihat definisi di atas di mana ia menyatakan bahwa fungsi callback dilewatkan ke beberapa fungsi lain dan pada titik tertentu disebut.
Dalam C ++ diinginkan untuk memiliki fungsi panggilan balik memanggil metode kelas. Ketika Anda melakukan ini, Anda memiliki akses ke data anggota. Jika Anda menggunakan cara C mendefinisikan panggilan balik, Anda harus mengarahkannya ke fungsi anggota statis. Ini sangat tidak diinginkan.
Inilah cara Anda dapat menggunakan panggilan balik di C ++. Asumsikan 4 file. Sepasang file .CPP / .H untuk setiap kelas. Kelas C1 adalah kelas dengan metode yang ingin kita panggil kembali. C2 memanggil kembali ke metode C1. Dalam contoh ini fungsi callback mengambil 1 parameter yang saya tambahkan demi pembaca. Contoh tidak menunjukkan objek apa pun yang dipakai dan digunakan. Satu kasus penggunaan untuk implementasi ini adalah ketika Anda memiliki satu kelas yang membaca dan menyimpan data ke dalam ruang sementara dan yang lain yang memposting memproses data. Dengan fungsi panggilan balik, untuk setiap baris data, baca panggilan balik kemudian dapat memprosesnya. Teknik ini memotong overhead ruang sementara yang diperlukan. Ini sangat berguna untuk query SQL yang mengembalikan sejumlah besar data yang kemudian harus diproses pasca.
sumber
Signal2 Boost memungkinkan Anda untuk berlangganan fungsi anggota generik (tanpa templat!) Dan dengan cara yang aman.
sumber
Jawaban yang diterima komprehensif tetapi terkait dengan pertanyaan saya hanya ingin memberikan contoh sederhana di sini. Saya punya kode yang sudah lama saya tulis. saya ingin melintasi pohon dengan cara berurutan (simpul kiri lalu root-simpul kemudian kanan-simpul) dan setiap kali saya mencapai satu Node saya ingin dapat memanggil fungsi sewenang-wenang sehingga bisa melakukan segalanya.
sumber