Jika fungsi callback diketahui pada waktu kompilasi, pertimbangkan template sebagai gantinya.
Baum mit Augen
4
Saat mengimplementasikan fungsi callback, Anda harus melakukan apa pun yang diminta pemanggil. Jika pertanyaan Anda benar-benar tentang merancang antarmuka panggilan balik, tidak ada informasi yang cukup di sini untuk menjawabnya. Apa yang Anda ingin penerima panggilan balik lakukan? Informasi apa yang perlu Anda berikan kepada penerima? Informasi apa yang harus diteruskan penerima kepada Anda sebagai hasil dari panggilan tersebut?
Singkatnya, gunakanstd::function kecuali Anda punya alasan untuk tidak melakukannya.
Pointer fungsi memiliki kelemahan karena tidak dapat menangkap beberapa konteks. Anda tidak akan dapat, misalnya, meneruskan fungsi lambda sebagai callback yang menangkap beberapa variabel konteks (tetapi akan berfungsi jika tidak menangkap apa pun). Memanggil variabel anggota suatu objek (yaitu non-statis) juga tidak dimungkinkan, karena objek ( this-pointer) perlu ditangkap. (1)
std::function(karena C ++ 11) pada dasarnya untuk menyimpan sebuah fungsi (menyebarkannya tidak mengharuskannya untuk disimpan). Karenanya, jika Anda ingin menyimpan callback misalnya dalam variabel anggota, itu mungkin pilihan terbaik Anda. Tetapi juga jika Anda tidak menyimpannya, ini adalah "pilihan pertama" yang baik meskipun memiliki kelemahan dalam memperkenalkan beberapa overhead (sangat kecil) saat dipanggil (jadi dalam situasi yang sangat kritis terhadap kinerja ini mungkin menjadi masalah tetapi di sebagian besar seharusnya tidak). Ini sangat "universal": jika Anda sangat peduli tentang kode yang konsisten dan dapat dibaca serta tidak ingin memikirkan setiap pilihan yang Anda buat (yaitu ingin membuatnya tetap sederhana), gunakan std::functionuntuk setiap fungsi yang Anda bagikan.
Pikirkan tentang opsi ketiga: Jika Anda akan mengimplementasikan fungsi kecil yang kemudian melaporkan sesuatu melalui fungsi panggilan balik yang disediakan, pertimbangkan parameter template , yang kemudian dapat berupa objek yang dapat dipanggil , yaitu penunjuk fungsi, functor, lambda, a std::function, ... Kekurangannya di sini adalah bahwa fungsi (luar) Anda menjadi template dan karenanya perlu diimplementasikan di header. Di sisi lain, Anda mendapatkan keuntungan bahwa panggilan ke callback bisa menjadi inline, karena kode klien dari fungsi (luar) Anda "melihat" panggilan ke callback akan menyediakan informasi jenis yang tepat.
Contoh untuk versi dengan parameter template (tulis &sebagai ganti &&untuk pra-C ++ 11):
Seperti yang Anda lihat pada tabel berikut, semuanya memiliki kelebihan dan kekurangan:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Ada solusi untuk mengatasi batasan ini, misalnya meneruskan data tambahan sebagai parameter lebih lanjut ke fungsi (luar) Anda: myFunction(..., callback, data)akan memanggil callback(data). Itu adalah "callback dengan argumen" gaya-C, yang dimungkinkan di C ++ (dan dengan cara yang banyak digunakan di WIN32 API) tetapi harus dihindari karena kita memiliki opsi yang lebih baik di C ++.
(2) Kecuali jika kita berbicara tentang templat kelas, yaitu kelas tempat Anda menyimpan fungsi adalah templat. Tetapi itu berarti bahwa di sisi klien, jenis fungsi memutuskan jenis objek yang menyimpan callback, yang hampir tidak pernah menjadi opsi untuk kasus penggunaan yang sebenarnya.
pointer fungsi memiliki overhead panggilan dibandingkan dengan parameter template. parameter template mempermudah penyebarisan, bahkan jika Anda diturunkan ke lebih dari satu tingkat, karena kode yang dijalankan dijelaskan oleh jenis parameter bukan nilainya. Dan objek fungsi templat yang disimpan dalam tipe kembalian templat adalah pola yang umum dan berguna (dengan konstruktor salinan yang baik, Anda dapat membuat fungsi templat yang efisien yang dapat diubah menjadi std::functiontipe yang dihapus jika Anda perlu segera menyimpannya di luar disebut konteks).
Yakk - Adam Nevraumont
1
@tohecz Saya sekarang menyebutkan apakah itu membutuhkan C ++ 11 atau tidak.
@MooingDuck Tentu saja tergantung pada implementasinya. Tetapi jika saya ingat dengan benar, karena cara kerja penghapusan tipe, ada satu lagi tipuan yang terjadi? Tapi sekarang saya memikirkannya lagi, saya rasa ini tidak terjadi jika Anda menetapkan pointer fungsi atau
lambda tanpa
1
@leemes: Benar, untuk function pointers atau captureless lambda, itu harus memiliki overhead yang sama dengan c-func-ptr. Yang masih warung pipa + tidak sebaris sepele.
Mooing Duck
26
void (*callbackFunc)(int); mungkin fungsi panggilan balik gaya C, tetapi fungsi ini sangat tidak dapat digunakan dengan desain yang buruk.
Callback gaya C yang dirancang dengan baik terlihat seperti void (*callbackFunc)(void*, int);- ia memiliki izin void*untuk memungkinkan kode yang melakukan callback mempertahankan status di luar fungsi. Tidak melakukan ini akan memaksa pemanggil untuk menyimpan status secara global, yang tidak sopan.
std::function< int(int) >akhirnya menjadi sedikit lebih mahal daripada int(*)(void*, int)pemanggilan di sebagian besar implementasi. Namun, lebih sulit bagi beberapa kompiler untuk melakukan inline. Ada std::functionimplementasi klon yang menyaingi overhead pemanggilan penunjuk fungsi (lihat 'delegasi tercepat yang mungkin', dll.) Yang mungkin masuk ke perpustakaan.
Sekarang, klien sistem callback sering kali perlu menyiapkan sumber daya dan membuangnya saat callback dibuat dan dihapus, dan untuk mengetahui masa pakai callback. void(*callback)(void*, int)tidak menyediakan ini.
Terkadang ini tersedia melalui struktur kode (masa pakai callback terbatas) atau melalui mekanisme lain (batalkan pendaftaran callback dan sejenisnya).
std::function menyediakan sarana untuk manajemen seumur hidup terbatas (salinan terakhir dari objek akan hilang jika dilupakan).
Secara umum, saya akan menggunakan std::functionkecuali masalah kinerja nyata. Jika ya, pertama-tama saya akan mencari perubahan struktural (alih-alih callback per-piksel, bagaimana dengan membuat prosesor scanline berdasarkan lambda yang Anda berikan kepada saya? Yang seharusnya cukup untuk mengurangi overhead panggilan fungsi ke tingkat yang sepele. ). Kemudian, jika tetap ada, saya akan menulis delegateberdasarkan kemungkinan delegasi tercepat, dan melihat apakah masalah kinerja hilang.
Saya kebanyakan hanya akan menggunakan pointer fungsi untuk API lama, atau untuk membuat antarmuka C untuk berkomunikasi antara kode yang dihasilkan kompiler berbeda. Saya juga telah menggunakannya sebagai detail implementasi internal ketika saya mengimplementasikan tabel lompat, penghapusan tipe, dll: ketika saya memproduksi dan mengkonsumsinya, dan saya tidak mengeksposnya secara eksternal untuk kode klien apa pun untuk digunakan, dan penunjuk fungsi melakukan semua yang saya butuhkan .
Perhatikan bahwa Anda bisa menulis pembungkus yang mengubah a std::function<int(int)>menjadi int(void*,int)gaya callback, dengan asumsi ada infrastruktur pengelolaan masa pakai callback yang tepat. Jadi sebagai tes asap untuk sistem manajemen seumur hidup callback C-style, saya akan memastikan bahwa membungkus std::functionbekerja dengan cukup baik.
Dari mana void*asalnya ini ? Mengapa Anda ingin mempertahankan status di luar fungsi? Suatu fungsi harus berisi semua kode yang dibutuhkannya, semua fungsionalitas, Anda cukup meneruskannya ke argumen yang diinginkan dan memodifikasi dan mengembalikan sesuatu. Jika Anda memerlukan beberapa status eksternal lalu mengapa functionPtr atau callback membawa bagasi itu? Saya pikir panggilan balik itu tidak perlu rumit.
Nikos
@ nik-lz Saya tidak yakin bagaimana saya akan mengajari Anda penggunaan dan sejarah callback di C dalam komentar. Atau filosofi prosedural sebagai lawan dari pemrograman fungsional. Jadi, Anda akan membiarkannya tidak terpenuhi.
Yakk - Adam Nevraumont
Aku lupa this. Apakah karena seseorang harus memperhitungkan kasus fungsi anggota yang dipanggil, jadi kita memerlukan thispenunjuk untuk menunjuk ke alamat objek? Jika saya salah, bisakah Anda memberi saya tautan ke tempat saya dapat menemukan info lebih lanjut tentang ini, karena saya tidak dapat menemukan banyak tentang itu. Terima kasih sebelumnya.
Nikos
@ Fungsi anggota Nik-Lz bukanlah fungsi. Fungsi tidak memiliki status (waktu proses). Callback mengambil void*untuk mengizinkan transmisi status runtime. Sebuah fungsi pointer dengan void*dan void*argumen dapat meniru fungsi anggota panggilan untuk sebuah objek. Maaf, saya tidak tahu tentang resource yang menjalankan "mendesain mekanisme callback C 101".
Yakk - Adam Nevraumont
Ya, itulah yang saya bicarakan. Status waktu proses pada dasarnya adalah alamat dari objek yang dipanggil (karena berubah di antara proses). Ini masih tentang this. Itu yang saya maksud. Ok, terima kasih.
Nikos
17
Gunakan std::functionuntuk menyimpan objek callable arbitrer. Ini memungkinkan pengguna untuk memberikan konteks apa pun yang diperlukan untuk callback; pointer fungsi biasa tidak.
Jika Anda memang perlu menggunakan pointer fungsi biasa karena suatu alasan (mungkin karena Anda menginginkan API yang kompatibel dengan C), Anda harus menambahkan void * user_contextargumen sehingga setidaknya memungkinkan (meskipun tidak nyaman) untuk mengakses status yang tidak langsung diteruskan ke fungsi.
Apa jenis p di sini? akankah itu menjadi tipe std :: function? batal f () {}; otomatis p = f; p ();
sree
14
Satu-satunya alasan yang harus dihindari std::functionadalah dukungan dari compiler lama yang tidak memiliki dukungan untuk template ini, yang telah diperkenalkan di C ++ 11.
Jika mendukung bahasa pra-C ++ 11 tidak diwajibkan, penggunaan akan std::functionmemberi penelepon Anda lebih banyak pilihan dalam mengimplementasikan callback, menjadikannya opsi yang lebih baik dibandingkan dengan pointer fungsi "biasa". Ini menawarkan lebih banyak pilihan kepada pengguna API Anda, sambil mengabstraksi penerapannya secara spesifik untuk kode Anda yang melakukan callback.
Jawaban:
Singkatnya, gunakan
std::function
kecuali Anda punya alasan untuk tidak melakukannya.Pointer fungsi memiliki kelemahan karena tidak dapat menangkap beberapa konteks. Anda tidak akan dapat, misalnya, meneruskan fungsi lambda sebagai callback yang menangkap beberapa variabel konteks (tetapi akan berfungsi jika tidak menangkap apa pun). Memanggil variabel anggota suatu objek (yaitu non-statis) juga tidak dimungkinkan, karena objek (
this
-pointer) perlu ditangkap. (1)std::function
(karena C ++ 11) pada dasarnya untuk menyimpan sebuah fungsi (menyebarkannya tidak mengharuskannya untuk disimpan). Karenanya, jika Anda ingin menyimpan callback misalnya dalam variabel anggota, itu mungkin pilihan terbaik Anda. Tetapi juga jika Anda tidak menyimpannya, ini adalah "pilihan pertama" yang baik meskipun memiliki kelemahan dalam memperkenalkan beberapa overhead (sangat kecil) saat dipanggil (jadi dalam situasi yang sangat kritis terhadap kinerja ini mungkin menjadi masalah tetapi di sebagian besar seharusnya tidak). Ini sangat "universal": jika Anda sangat peduli tentang kode yang konsisten dan dapat dibaca serta tidak ingin memikirkan setiap pilihan yang Anda buat (yaitu ingin membuatnya tetap sederhana), gunakanstd::function
untuk setiap fungsi yang Anda bagikan.Pikirkan tentang opsi ketiga: Jika Anda akan mengimplementasikan fungsi kecil yang kemudian melaporkan sesuatu melalui fungsi panggilan balik yang disediakan, pertimbangkan parameter template , yang kemudian dapat berupa objek yang dapat dipanggil , yaitu penunjuk fungsi, functor, lambda, a
std::function
, ... Kekurangannya di sini adalah bahwa fungsi (luar) Anda menjadi template dan karenanya perlu diimplementasikan di header. Di sisi lain, Anda mendapatkan keuntungan bahwa panggilan ke callback bisa menjadi inline, karena kode klien dari fungsi (luar) Anda "melihat" panggilan ke callback akan menyediakan informasi jenis yang tepat.Contoh untuk versi dengan parameter template (tulis
&
sebagai ganti&&
untuk pra-C ++ 11):template <typename CallbackFunction> void myFunction(..., CallbackFunction && callback) { ... callback(...); ... }
Seperti yang Anda lihat pada tabel berikut, semuanya memiliki kelebihan dan kekurangan:
(1) Ada solusi untuk mengatasi batasan ini, misalnya meneruskan data tambahan sebagai parameter lebih lanjut ke fungsi (luar) Anda:
myFunction(..., callback, data)
akan memanggilcallback(data)
. Itu adalah "callback dengan argumen" gaya-C, yang dimungkinkan di C ++ (dan dengan cara yang banyak digunakan di WIN32 API) tetapi harus dihindari karena kita memiliki opsi yang lebih baik di C ++.(2) Kecuali jika kita berbicara tentang templat kelas, yaitu kelas tempat Anda menyimpan fungsi adalah templat. Tetapi itu berarti bahwa di sisi klien, jenis fungsi memutuskan jenis objek yang menyimpan callback, yang hampir tidak pernah menjadi opsi untuk kasus penggunaan yang sebenarnya.
(3) Untuk pra-C ++ 11, gunakan
boost::function
sumber
std::function
tipe yang dihapus jika Anda perlu segera menyimpannya di luar disebut konteks).void (*callbackFunc)(int);
mungkin fungsi panggilan balik gaya C, tetapi fungsi ini sangat tidak dapat digunakan dengan desain yang buruk.Callback gaya C yang dirancang dengan baik terlihat seperti
void (*callbackFunc)(void*, int);
- ia memiliki izinvoid*
untuk memungkinkan kode yang melakukan callback mempertahankan status di luar fungsi. Tidak melakukan ini akan memaksa pemanggil untuk menyimpan status secara global, yang tidak sopan.std::function< int(int) >
akhirnya menjadi sedikit lebih mahal daripadaint(*)(void*, int)
pemanggilan di sebagian besar implementasi. Namun, lebih sulit bagi beberapa kompiler untuk melakukan inline. Adastd::function
implementasi klon yang menyaingi overhead pemanggilan penunjuk fungsi (lihat 'delegasi tercepat yang mungkin', dll.) Yang mungkin masuk ke perpustakaan.Sekarang, klien sistem callback sering kali perlu menyiapkan sumber daya dan membuangnya saat callback dibuat dan dihapus, dan untuk mengetahui masa pakai callback.
void(*callback)(void*, int)
tidak menyediakan ini.Terkadang ini tersedia melalui struktur kode (masa pakai callback terbatas) atau melalui mekanisme lain (batalkan pendaftaran callback dan sejenisnya).
std::function
menyediakan sarana untuk manajemen seumur hidup terbatas (salinan terakhir dari objek akan hilang jika dilupakan).Secara umum, saya akan menggunakan
std::function
kecuali masalah kinerja nyata. Jika ya, pertama-tama saya akan mencari perubahan struktural (alih-alih callback per-piksel, bagaimana dengan membuat prosesor scanline berdasarkan lambda yang Anda berikan kepada saya? Yang seharusnya cukup untuk mengurangi overhead panggilan fungsi ke tingkat yang sepele. ). Kemudian, jika tetap ada, saya akan menulisdelegate
berdasarkan kemungkinan delegasi tercepat, dan melihat apakah masalah kinerja hilang.Saya kebanyakan hanya akan menggunakan pointer fungsi untuk API lama, atau untuk membuat antarmuka C untuk berkomunikasi antara kode yang dihasilkan kompiler berbeda. Saya juga telah menggunakannya sebagai detail implementasi internal ketika saya mengimplementasikan tabel lompat, penghapusan tipe, dll: ketika saya memproduksi dan mengkonsumsinya, dan saya tidak mengeksposnya secara eksternal untuk kode klien apa pun untuk digunakan, dan penunjuk fungsi melakukan semua yang saya butuhkan .
Perhatikan bahwa Anda bisa menulis pembungkus yang mengubah a
std::function<int(int)>
menjadiint(void*,int)
gaya callback, dengan asumsi ada infrastruktur pengelolaan masa pakai callback yang tepat. Jadi sebagai tes asap untuk sistem manajemen seumur hidup callback C-style, saya akan memastikan bahwa membungkusstd::function
bekerja dengan cukup baik.sumber
void*
asalnya ini ? Mengapa Anda ingin mempertahankan status di luar fungsi? Suatu fungsi harus berisi semua kode yang dibutuhkannya, semua fungsionalitas, Anda cukup meneruskannya ke argumen yang diinginkan dan memodifikasi dan mengembalikan sesuatu. Jika Anda memerlukan beberapa status eksternal lalu mengapa functionPtr atau callback membawa bagasi itu? Saya pikir panggilan balik itu tidak perlu rumit.this
. Apakah karena seseorang harus memperhitungkan kasus fungsi anggota yang dipanggil, jadi kita memerlukanthis
penunjuk untuk menunjuk ke alamat objek? Jika saya salah, bisakah Anda memberi saya tautan ke tempat saya dapat menemukan info lebih lanjut tentang ini, karena saya tidak dapat menemukan banyak tentang itu. Terima kasih sebelumnya.void*
untuk mengizinkan transmisi status runtime. Sebuah fungsi pointer denganvoid*
danvoid*
argumen dapat meniru fungsi anggota panggilan untuk sebuah objek. Maaf, saya tidak tahu tentang resource yang menjalankan "mendesain mekanisme callback C 101".this
. Itu yang saya maksud. Ok, terima kasih.Gunakan
std::function
untuk menyimpan objek callable arbitrer. Ini memungkinkan pengguna untuk memberikan konteks apa pun yang diperlukan untuk callback; pointer fungsi biasa tidak.Jika Anda memang perlu menggunakan pointer fungsi biasa karena suatu alasan (mungkin karena Anda menginginkan API yang kompatibel dengan C), Anda harus menambahkan
void * user_context
argumen sehingga setidaknya memungkinkan (meskipun tidak nyaman) untuk mengakses status yang tidak langsung diteruskan ke fungsi.sumber
Satu-satunya alasan yang harus dihindari
std::function
adalah dukungan dari compiler lama yang tidak memiliki dukungan untuk template ini, yang telah diperkenalkan di C ++ 11.Jika mendukung bahasa pra-C ++ 11 tidak diwajibkan, penggunaan akan
std::function
memberi penelepon Anda lebih banyak pilihan dalam mengimplementasikan callback, menjadikannya opsi yang lebih baik dibandingkan dengan pointer fungsi "biasa". Ini menawarkan lebih banyak pilihan kepada pengguna API Anda, sambil mengabstraksi penerapannya secara spesifik untuk kode Anda yang melakukan callback.sumber
std::function
mungkin membawa VMT ke kode dalam beberapa kasus, yang berdampak pada kinerja.sumber