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.
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. Misalnya for_each
algoritme menerapkan panggilan balik unary ke setiap item dalam rentang iterator:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
yang dapat digunakan untuk kenaikan pertama dan kemudian mencetak vektor dengan melewatkan perangkat yang sesuai misalnya:
std::vector<double> v{ 1.0, 2.2, 4.0, 5.5, 7.2 };
double r = 4.0;
std::for_each(v.begin(), v.end(), [&](double & v) { v += r; });
std::for_each(v.begin(), v.end(), [](double v) { std::cout << v << " "; });
yang mencetak
5 6.2 8 9.5 11.2
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.
void player_jump();
void player_crouch();
class game_core
{
std::array<void(*)(), total_num_keys> actions;
//
void key_pressed(unsigned key_id)
{
if(actions[key_id]) actions[key_id]();
}
// update keybind from menu
void update_keybind(unsigned key_id, void(*new_action)())
{
actions[key_id] = new_action;
}
};
Di sini fungsi key_pressed
menggunakan panggilan balik yang disimpan actions
untuk mendapatkan perilaku yang diinginkan ketika tombol tertentu ditekan. Jika pemain memilih untuk mengubah tombol untuk melompat, mesin dapat memanggil
game_core_instance.update_keybind(newly_selected_key, &player_jump);
dan dengan demikian mengubah perilaku panggilan ke key_pressed
(panggilan mana player_jump
) setelah tombol ini ditekan pada saat ingame berikutnya.
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.
Catatan: Pada C ++ 17, panggilan seperti f(...)
dapat ditulis sebagai std::invoke(f, ...)
yang juga menangani pointer ke case anggota.
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
:
int foo (int x) { return 2+x; }
Sebuah tipe fungsi pointer memiliki notasi
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
di mana jenis penunjuk fungsi bernama akan terlihat seperti
return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. f_int_t is a type: function pointer taking one int argument, returning int
typedef int (*f_int_t) (int);
// foo_p is a pointer to function taking int returning int
// initialized by pointer to function foo taking int returning int
int (* foo_p)(int) = &foo;
// can alternatively be written as
f_int_t foo_p = &foo;
The using
deklarasi memberi kita pilihan untuk membuat hal-hal sedikit lebih mudah dibaca, karena typedef
untuk f_int_t
juga dapat ditulis sebagai:
using f_int_t = int(*)(int);
Di mana (setidaknya bagi saya) lebih jelas bahwa itu f_int_t
adalah tipe baru alias dan pengenalan tipe fungsi pointer juga lebih mudah
Dan deklarasi fungsi yang menggunakan callback dari tipe pointer fungsi adalah:
// foobar having a callback argument named moo of type
// pointer to function returning int taking int as its argument
int foobar (int x, int (*moo)(int));
// if f_int is the function pointer typedef from above we can also write foobar as:
int foobar (int x, f_int_t moo);
Notasi panggilan mengikuti sintaks panggilan fungsi sederhana:
int foobar (int x, int (*moo)(int))
{
return x + moo(x); // function pointer moo called using argument x
}
// analog
int foobar (int x, f_int_t moo)
{
return x + moo(x); // function pointer moo called using argument x
}
Fungsi callback mengambil pointer fungsi dapat disebut menggunakan pointer fungsi.
Menggunakan fungsi yang mengambil callback fungsi pointer agak sederhana:
int a = 5;
int b = foobar(a, foo); // call foobar with pointer to foo as callback
// can also be
int b = foobar(a, &foo); // call foobar with pointer to foo as callback
Fungsi yang ditulis yang tidak bergantung pada cara kerja callback:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
di mana panggilan balik mungkin bisa
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
digunakan seperti
int a[5] = {1, 2, 3, 4, 5};
tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
tranform_every_int(&a[0], 5, square_int);
// now a == {4, 16, 36, 64, 100};
Sebuah pointer ke fungsi anggota (dari beberapa kelas C
) adalah tipe khusus dari (dan bahkan lebih kompleks) pointer fungsi yang membutuhkan objek tipe C
untuk beroperasi.
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Sebuah pointer ke tipe fungsi anggota untuk beberapa kelas T
memiliki notasi
// can have more or less parameters
return_type (T::*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to C::foo has the type
int (C::*) (int)
di mana penunjuk bernama untuk fungsi anggota akan -di analogi dengan fungsi penunjuk- terlihat seperti ini:
return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a type `f_C_int` representing a pointer to member function of `C`
// taking int returning int is:
typedef int (C::* f_C_int_t) (int x);
// The type of C_foo_p is a pointer to member function of C taking int returning int
// Its value is initialized by a pointer to foo of C
int (C::* C_foo_p)(int) = &C::foo;
// which can also be written using the typedef:
f_C_int_t C_foo_p = &C::foo;
Contoh: Mendeklarasikan fungsi yang mengambil pointer ke fungsi panggilan balik anggota sebagai salah satu argumennya:
// C_foobar having an argument named moo of type pointer to member function of C
// where the callback returns int taking int as its argument
// also needs an object of type c
int C_foobar (int x, C const &c, int (C::*moo)(int));
// can equivalently declared using the typedef above:
int C_foobar (int x, C const &c, f_C_int_t moo);
Fungsi pointer ke anggota C
dapat dipanggil, sehubungan dengan objek tipe C
dengan menggunakan operasi akses anggota pada pointer dereferenced.
Catatan: Diperlukan Parenthesis!
int C_foobar (int x, C const &c, int (C::*moo)(int))
{
return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
// analog
int C_foobar (int x, C const &c, f_C_int_t moo)
{
return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
Catatan: Jika sebuah pointer ke C
tersedia, sintaksinya setara (di mana pointer juga C
harus didereferensi):
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
if (!c) return x;
// function pointer meow called for object *c using argument x
return x + ((*c).*meow)(x);
}
// or equivalent:
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
if (!c) return x;
// function pointer meow called for object *c using argument x
return x + (c->*meow)(x);
}
Fungsi callback mengambil pointer fungsi anggota kelas T
dapat dipanggil menggunakan pointer fungsi anggota kelas T
.
Menggunakan fungsi yang mengambil pointer ke fungsi anggota callback adalah -di analogi dengan fungsi pointer- cukup sederhana juga:
C my_c{2}; // aggregate initialization
int a = 5;
int b = C_foobar(a, my_c, &C::foo); // call C_foobar with pointer to foo as its callback
std::function
objek (header <functional>
)The std::function
kelas adalah fungsi wrapper polimorfik untuk menyimpan, menyalin atau Invoke callables.
std::function
notasi objek / tipeJenis std::function
objek menyimpan callable terlihat seperti:
std::function<return_type(parameter_type_1, parameter_type_2, parameter_type_3)>
// i.e. using the above function declaration of foo:
std::function<int(int)> stdf_foo = &foo;
// or C::foo:
std::function<int(const C&, int)> stdf_C_foo = &C::foo;
Kelas std::function
telah operator()
ditentukan yang dapat digunakan untuk menjalankan targetnya.
int stdf_foobar (int x, std::function<int(int)> moo)
{
return x + moo(x); // std::function moo called
}
// or
int stdf_C_foobar (int x, C const &c, std::function<int(C const &, int)> moo)
{
return x + moo(c, x); // std::function moo called using c and x
}
The std::function
callback lebih generik dari pointer fungsi atau pointer ke fungsi anggota karena jenis yang berbeda dapat berlalu dan secara implisit diubah menjadi std::function
objek.
3.3.1 Pointer fungsi dan pointer ke fungsi anggota
Pointer fungsi
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
atau pointer ke fungsi anggota
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
dapat digunakan.
3.3.2 Ekspresi Lambda
Penutupan tanpa ekspresi dari ekspresi lambda dapat disimpan di std::function
objek:
int a = 2;
int c = 3;
int b = stdf_foobar(a, [c](int x) -> int { return 7+c*x; });
// b == 15 == a + (7*c*a) == 2 + (7+3*2)
3.3.3 std::bind
ekspresi
Hasil dari std::bind
ekspresi dapat dilewati. Misalnya dengan mengikat parameter ke panggilan fungsi pointer:
int foo_2 (int x, int y) { return 9*x + y; }
using std::placeholders::_1;
int a = 2;
int b = stdf_foobar(a, std::bind(foo_2, _1, 3));
// b == 23 == 2 + ( 9*2 + 3 )
int c = stdf_foobar(a, std::bind(foo_2, 5, _1));
// c == 49 == 2 + ( 9*5 + 2 )
Di mana juga objek dapat diikat sebagai objek untuk pemanggilan pointer ke fungsi anggota:
int a = 2;
C const my_c{7}; // aggregate initialization
int b = stdf_foobar(a, std::bind(&C::foo, my_c, _1));
// b == 1 == 2 + ( 2 + 7 )
3.3.4 Objek fungsi
Objek kelas yang memiliki operator()
kelebihan yang tepat dapat disimpan di dalam std::function
objek juga.
struct Meow
{
int y = 0;
Meow(int y_) : y(y_) {}
int operator()(int x) { return y * x; }
};
int a = 11;
int b = stdf_foobar(a, Meow{8});
// b == 99 == 11 + ( 8 * 11 )
Mengubah contoh penunjuk fungsi untuk digunakan std::function
void stdf_tranform_every_int(int * v, unsigned n, std::function<int(int)> fp)
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
memberi jauh lebih banyak utilitas untuk fungsi itu karena (lihat 3.3) kami memiliki lebih banyak kemungkinan untuk menggunakannya:
// using function pointer still possible
int a[5] = {1, 2, 3, 4, 5};
stdf_tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
// use it without having to write another function by using a lambda
stdf_tranform_every_int(&a[0], 5, [](int x) -> int { return x/2; });
// now a == {1, 2, 3, 4, 5}; again
// use std::bind :
int nine_x_and_y (int x, int y) { return 9*x + y; }
using std::placeholders::_1;
// calls nine_x_and_y for every int in a with y being 4 every time
stdf_tranform_every_int(&a[0], 5, std::bind(nine_x_and_y, _1, 4));
// now a == {13, 22, 31, 40, 49};
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.
Generalisasi yaitu std_ftransform_every_int
kode dari atas lebih jauh dapat dicapai dengan menggunakan templat:
template<class R, class T>
void stdf_transform_every_int_templ(int * v,
unsigned const n, std::function<R(T)> fp)
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
dengan sintaks yang lebih umum (dan juga termudah) untuk tipe panggilan balik menjadi argumen templated sederhana yang akan dideduksi:
template<class F>
void transform_every_int_templ(int * v,
unsigned const n, F f)
{
std::cout << "transform_every_int_templ<"
<< type_name<F>() << ">\n";
for (unsigned i = 0; i < n; ++i)
{
v[i] = f(v[i]);
}
}
Catatan: Output yang disertakan mencetak nama jenis yang disimpulkan untuk jenis templated F
. Implementasi type_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.
template<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first,
UnaryOperation unary_op)
{
while (first1 != last1) {
*d_first++ = unary_op(*first1++);
}
return d_first;
}
Tipe yang kompatibel untuk std::function
metode callback templated stdf_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:
// Let
int foo (int x) { return 2+x; }
int muh (int const &x) { return 3+x; }
int & woof (int &x) { x *= 4; return x; }
int a[5] = {1, 2, 3, 4, 5};
stdf_transform_every_int_templ<int,int>(&a[0], 5, &foo);
// a == {3, 4, 5, 6, 7}
stdf_transform_every_int_templ<int, int const &>(&a[0], 5, &muh);
// a == {6, 7, 8, 9, 10}
stdf_transform_every_int_templ<int, int &>(&a[0], 5, &woof);
Catatan: std_ftransform_every_int
(versi non templated; lihat di atas) berfungsi dengan foo
tetapi tidak menggunakan muh
.
// Let
void print_int(int * p, unsigned const n)
{
bool f{ true };
for (unsigned i = 0; i < n; ++i)
{
std::cout << (f ? "" : " ") << p[i];
f = false;
}
std::cout << "\n";
}
Parameter templated polos dari transform_every_int_templ
dapat setiap jenis yang bisa dipanggil.
int a[5] = { 1, 2, 3, 4, 5 };
print_int(a, 5);
transform_every_int_templ(&a[0], 5, foo);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, muh);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, woof);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, [](int x) -> int { return x + x + x; });
print_int(a, 5);
transform_every_int_templ(&a[0], 5, Meow{ 4 });
print_int(a, 5);
using std::placeholders::_1;
transform_every_int_templ(&a[0], 5, std::bind(foo_2, _1, 3));
print_int(a, 5);
transform_every_int_templ(&a[0], 5, std::function<int(int)>{&foo});
print_int(a, 5);
Kode di atas mencetak:
1 2 3 4 5
transform_every_int_templ <int(*)(int)>
3 4 5 6 7
transform_every_int_templ <int(*)(int&)>
6 8 10 12 14
transform_every_int_templ <int& (*)(int&)>
9 11 13 15 17
transform_every_int_templ <main::{lambda(int)#1} >
27 33 39 45 51
transform_every_int_templ <Meow>
108 132 156 180 204
transform_every_int_templ <std::_Bind<int(*(std::_Placeholder<1>, int))(int, int)>>
975 1191 1407 1623 1839
transform_every_int_templ <std::function<int(int)>>
977 1193 1409 1625 1841
type_name
implementasi yang digunakan di atas#include <type_traits>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
template <class T>
std::string type_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(abi::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr), std::free);
std::string r = own != nullptr?own.get():typeid(TR).name();
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += " &";
else if (std::is_rvalue_reference<T>::value)
r += " &&";
return r;
}
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
//Define a type for the callback signature,
//it is not necessary, but makes life easier
//Function pointer called CallbackType that takes a float
//and returns an int
typedef int (*CallbackType)(float);
void DoWork(CallbackType callback)
{
float variable = 0.0f;
//Do calculations
//Call the callback with the variable, and retrieve the
//result
int result = callback(variable);
//Do something with the result
}
int SomeCallback(float variable)
{
int result;
//Interpret variable
return result;
}
int main(int argc, char ** argv)
{
//Pass in SomeCallback to the DoWork
DoWork(&SomeCallback);
}
Sekarang jika Anda ingin meneruskan metode kelas sebagai panggilan balik, deklarasi ke pointer fungsi tersebut memiliki deklarasi yang lebih kompleks, contoh:
//Declaration:
typedef int (ClassName::*CallbackType)(float);
//This method performs work using an object instance
void DoWorkObject(CallbackType callback)
{
//Class instance to invoke it through
ClassName objectInstance;
//Invocation
int result = (objectInstance.*callback)(1.0f);
}
//This method performs work using an object pointer
void DoWorkPointer(CallbackType callback)
{
//Class pointer to invoke it through
ClassName * pointerInstance;
//Invocation
int result = (pointerInstance->*callback)(1.0f);
}
int main(int argc, char ** argv)
{
//Pass in SomeCallback to the DoWork
DoWorkObject(&ClassName::Method);
DoWorkPointer(&ClassName::Method);
}
typedef
tipe panggilan balik? Apakah itu mungkin?
typedef
hanyalah gula sintaksis agar lebih mudah dibaca. Tanpa typedef
, 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:
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
typedef std::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf)
{ }
int healthValue() const { return healthFunc(*this); }
private:
HealthCalcFunc healthFunc;
};
Saya pikir contoh mengatakan semuanya.
std::function<>
adalah cara "modern" untuk menulis panggilan balik C ++.
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.
Jawaban yang diterima sangat bermanfaat dan cukup komprehensif. Namun, OP menyatakan
Saya ingin melihat contoh sederhana untuk menulis fungsi panggilan balik.
Jadi di sini Anda mulai, dari C ++ 11 yang Anda miliki std::function
sehingga tidak perlu pointer fungsi dan hal-hal serupa:
#include <functional>
#include <string>
#include <iostream>
void print_hashes(std::function<int (const std::string&)> hash_calculator) {
std::string strings_to_hash[] = {"you", "saved", "my", "day"};
for(auto s : strings_to_hash)
std::cout << s << ":" << hash_calculator(s) << std::endl;
}
int main() {
print_hashes( [](const std::string& str) { /** lambda expression */
int result = 0;
for (int i = 0; i < str.length(); i++)
result += pow(31, i) * str.at(i);
return result;
});
return 0;
}
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 adalah std::function<int (const std::string&)>
yang menggambarkan fungsi seperti argumen input dari fungsi yang akan memanggilnya.
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.
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
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.
/////////////////////////////////////////////////////////////////////
// C1 H file
class C1
{
public:
C1() {};
~C1() {};
void CALLBACK F1(int i);
};
/////////////////////////////////////////////////////////////////////
// C1 CPP file
void CALLBACK C1::F1(int i)
{
// Do stuff with C1, its methods and data, and even do stuff with the passed in parameter
}
/////////////////////////////////////////////////////////////////////
// C2 H File
class C1; // Forward declaration
class C2
{
typedef void (CALLBACK C1::* pfnCallBack)(int i);
public:
C2() {};
~C2() {};
void Fn(C1 * pThat,pfnCallBack pFn);
};
/////////////////////////////////////////////////////////////////////
// C2 CPP File
void C2::Fn(C1 * pThat,pfnCallBack pFn)
{
// Call a non-static method in C1
int i = 1;
(pThat->*pFn)(i);
}
Signal2 Boost memungkinkan Anda untuk berlangganan fungsi anggota generik (tanpa templat!) Dan dengan cara yang aman.
Contoh: Sinyal Tampilan Dokumen dapat digunakan untuk mengimplementasikan arsitektur Document-View yang fleksibel. Dokumen akan berisi sinyal yang dapat dihubungkan oleh setiap tampilan. Kelas Dokumen berikut mendefinisikan dokumen teks sederhana yang mendukung tampilan mulitple. Perhatikan bahwa ia menyimpan sinyal tunggal yang akan menghubungkan semua tampilan.
class Document
{
public:
typedef boost::signals2::signal<void ()> signal_t;
public:
Document()
{}
/* Connect a slot to the signal which will be emitted whenever
text is appended to the document. */
boost::signals2::connection connect(const signal_t::slot_type &subscriber)
{
return m_sig.connect(subscriber);
}
void append(const char* s)
{
m_text += s;
m_sig();
}
const std::string& getText() const
{
return m_text;
}
private:
signal_t m_sig;
std::string m_text;
};
Selanjutnya, kita dapat mulai mendefinisikan tampilan. Kelas TextView berikut memberikan tampilan sederhana dari teks dokumen.
class TextView
{
public:
TextView(Document& doc): m_document(doc)
{
m_connection = m_document.connect(boost::bind(&TextView::refresh, this));
}
~TextView()
{
m_connection.disconnect();
}
void refresh() const
{
std::cout << "TextView: " << m_document.getText() << std::endl;
}
private:
Document& m_document;
boost::signals2::connection m_connection;
};
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.
void inorder_traversal(Node *p, void *out, void (*callback)(Node *in, void *out))
{
if (p == NULL)
return;
inorder_traversal(p->left, out, callback);
callback(p, out); // call callback function like this.
inorder_traversal(p->right, out, callback);
}
// Function like bellow can be used in callback of inorder_traversal.
void foo(Node *t, void *out = NULL)
{
// You can just leave the out variable and working with specific node of tree. like bellow.
// cout << t->item;
// Or
// You can assign value to out variable like below
// Mention that the type of out is void * so that you must firstly cast it to your proper out.
*((int *)out) += 1;
}
// This function use inorder_travesal function to count the number of nodes existing in the tree.
void number_nodes(Node *t)
{
int sum = 0;
inorder_traversal(t, &sum, foo);
cout << sum;
}
int main()
{
Node *root = NULL;
// What These functions perform is inserting an integer into a Tree data-structure.
root = insert_tree(root, 6);
root = insert_tree(root, 3);
root = insert_tree(root, 8);
root = insert_tree(root, 7);
root = insert_tree(root, 9);
root = insert_tree(root, 10);
number_nodes(root);
}