Saat mengimplementasikan fungsi callback di C ++, apakah saya harus tetap menggunakan pointer fungsi C-style:
void (*callbackFunc)(int);
Atau haruskah saya menggunakan std :: function:
std::function< void(int) > callbackFunc;
Saat mengimplementasikan fungsi callback di C ++, apakah saya harus tetap menggunakan pointer fungsi C-style:
void (*callbackFunc)(int);
Atau haruskah saya menggunakan std :: function:
std::function< void(int) > callbackFunc;
Jawaban:
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::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:
+-------------------+--------------+---------------+----------------+
| | 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.
(3) Untuk pra-C ++ 11, gunakan boost::function
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 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::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 menulis delegate
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)>
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::function
bekerja dengan cukup baik.
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 memerlukan this
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 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".
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.
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.