Yanıtlar:
Not: Cevapların çoğu, C ++ 'da "geri arama" mantığını elde etmek için bir olasılık olan fonksiyon işaretçileri kapsar, ancak bugün itibariyle bence en uygun olanı değildir.
Geri arama, o geri aramaya bağlı olarak geçerli mantığı özelleştirmek için kullanılan bir sınıf veya işlev tarafından kabul edilen çağrılabilir (aşağıya bakınız).
Geri aramaların kullanılmasının bir nedeni , çağrılan işlevdeki mantıktan bağımsız olan ve farklı geri aramalarla yeniden kullanılabilen genel kod yazmaktır .
Standart algoritmalar kitaplığının birçok işlevi <algorithm>
geri çağrıları kullanır. Örneğin for_each
algoritma, bir dizi yineleyicideki her öğeye tekli bir geri arama uygular:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
bu, ilk önce arttırmak ve daha sonra örneğin uygun callables ileterek bir vektör yazdırmak için kullanılabilir:
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 << " "; });
hangi baskılar
5 6.2 8 9.5 11.2
Geri aramaların başka bir uygulaması, belirli olayların arayanlarının belirli bir miktar statik / derleme zamanı esnekliği sağlayan bildirimidir.
Şahsen, iki farklı geri arama kullanan bir yerel optimizasyon kütüphanesi kullanıyorum:
Bu nedenle, kütüphane tasarımcısı, bildirim geri araması yoluyla programlayıcıya verilen bilgilerle ne olacağına karar vermekle yükümlü değildir ve mantıksal geri arama tarafından sağlandıkları için işlev değerlerinin gerçekte nasıl belirleneceği konusunda endişelenmesine gerek yoktur. Bu şeyleri doğru yapmak kütüphane kullanıcısı nedeniyle bir görevdir ve kütüphaneyi ince ve daha genel tutar.
Ayrıca, geri çağrılar dinamik çalışma zamanı davranışını etkinleştirebilir.
İşe yarayan bir işlevi olan bir tür oyun motoru sınıfı düşünün, kullanıcılar klavyesinde her düğmeye bastığında ve oyun davranışınızı kontrol eden bir dizi işlev düşünün. Geri aramalarla çalışma zamanında hangi işlemin yapılacağına karar verebilirsiniz (yeniden).
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;
}
};
Burada işlev key_pressed
, actions
belirli bir tuşa basıldığında istenen davranışı elde etmek için saklanan geri çağrıları kullanır . Oyuncu zıplamak için düğmeyi değiştirmeyi seçerse, motor arayabilir
game_core_instance.update_keybind(newly_selected_key, &player_jump);
ve böylece bir sonraki oyunda bu düğmeye basıldığında key_pressed
(çağrının player_jump
) çağrısının davranışını değiştirin .
Daha resmi bir açıklama için bkz. C ++ kavramları: cppreference üzerinden çağrılabilir .
Geri arama işlevselliği C ++ (11) 'te çeşitli şekillerde gerçekleştirilebilir, çünkü birkaç farklı şey çağrılabilir hale gelir * :
std::function
nesnelerioperator()
)* Not: Veri üyelerine işaretçi de çağrılabilir ancak hiçbir işlev çağrılmaz.
Not: C ++ 17'den itibaren, işaretçiyi üye vakasına da işleyen benzer bir çağrı f(...)
yazılabilir std::invoke(f, ...)
.
Bir işlev işaretçisi bir geri çağrının sahip olabileceği en basit (genel olarak; okunabilirlik açısından en kötü) tipidir.
Basit bir işleve sahip olalım foo
:
int foo (int x) { return 2+x; }
Bir işlev işaretçisi türünde gösterimi vardır
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
burada adlandırılmış bir işlev işaretçisi türü
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;
using
Çünkü beyanı bize yapmak şeyler biraz daha okunabilir seçeneği sunar typedef
için f_int_t
ayrıca yazılabilir:
using f_int_t = int(*)(int);
Nerede (en azından benim için) f_int_t
yeni tür takma adı daha açık ve işlev işaretçisi türünün tanınması da daha kolay
Ve işlev işaretçisi türünün geri aramasını kullanan bir işlev bildirimi şöyle olacaktır:
// 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);
Çağrı gösterimi, basit işlev çağrısı sözdizimini izler:
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
}
İşlev işaretçisi kullanan bir geri arama işlevi, işlev işaretçileri kullanılarak çağrılabilir.
Bir işlev işaretçisi geri araması alan bir işlevi kullanmak oldukça basittir:
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
Geri aramanın nasıl çalıştığına bağlı olmayan bir işlev ca yazılabilir:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
mümkünse geri aramalar olabilir
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
gibi kullanılmış
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};
İşaretçi-üye işlevi (bazı sınıflardan C
), C
üzerinde çalışacak türde bir nesne gerektiren özel bir tür (ve daha karmaşık) işlev işaretleyicisidir .
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Bazı sınıflar için bir işaretçi-üye işlev türüT
gösterimi vardır
// 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)
nerede bir üye işleve adlı işaretçi böyle fonksiyon pointer- görünüme benzetme gibi laflar edecek:
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;
Örnek: Bağımsız değişkenlerden biri olarak üye işlev geri aramasına bir işaretçi alan bir işlevi bildirme :
// 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);
Öğesinin üye işlevine işaretçisi, kayıttan çıkarılmış işaretçideki üye erişim işlemleri kullanılarak C
türdeki bir nesneye göre çağrılabilir C
.
Not: Parantez gereklidir!
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
}
Not: Kullanılacak bir işaretçi varsa C
sözdizimi eşdeğerdir (işaretçinin C
de kaydı kaldırılmalıdır):
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);
}
Sınıfın üye işlev işaretçisini alan bir geri çağırma işlevi, sınıfın T
üye işlev işaretçisini kullanarak çağrılabilir T
.
Üye işlev geri aramasına bir işaretçi alan bir işlev kullanmak, işlev işaretçileriyle benzerlik gösterir - oldukça basittir:
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
nesneler (başlık <functional>
)std::function
Sınıf deposu, kopyalama ya da callables çağırmak polimorfik bir fonksiyonu sarıcı.
std::function
nesne / tip gösterimi yazmastd::function
Çağrılabilirleri saklayan bir nesnenin türü şuna benzer:
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;
Sınıf std::function
, operator()
hedefini çağırmak için kullanılabilecek tanımlamıştır.
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
}
std::function
Farklı geçirilir ve dolaylı olarak dönüştürülebilir çünkü geri arama işlevi işaretçileri veya üye işlevine işaretçi daha jenerik olduğundan, std::function
nesne.
3.3.1 İşlev işaretçileri ve üye işlevlerine işaretçiler
Bir işlev işaretçisi
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
veya üye işlevine bir işaretçi
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
kullanılabilir.
3.3.2 Lambda ifadeleri
Bir lambda ifadesinden isimsiz bir kapatma bir std::function
nesnede saklanabilir :
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
İfadeler
Bir std::bind
ifadenin sonucu geçirilebilir. Örneğin, parametreleri bir işlev işaretçisi çağrısına bağlayarak:
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 )
Ayrıca, nesnelerin işaretçiyi üye işlevlerine çağırması için nesne olarak da bağlanabilir:
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 İşlev nesneleri
operator()
Aşırı yüklenmeye sahip sınıfların nesneleri de bir std::function
nesnenin içinde saklanabilir .
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 )
Kullanılacak işlev işaretçisi örneğini değiştirme 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]);
}
}
bu işleve çok daha fazla fayda sağlar, çünkü (bkz. 3.3) onu kullanmak için daha fazla imkanımız var:
// 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};
Şablonları kullanarak, geri aramayı çağıran kod, std::function
nesneleri kullanmaktan daha genel olabilir .
Şablonların bir derleme zamanı özelliği olduğunu ve derleme zamanı polimorfizmi için bir tasarım aracı olduğunu unutmayın. Çalışma zamanı dinamik davranışı geri çağrılar yoluyla sağlanacaksa, şablonlar yardımcı olur ancak çalışma zamanı dinamiklerini tetiklemezler.
Genelleme yani std_ftransform_every_int
yukarıdaki kod daha da ileri giderek şablonlar kullanılarak elde edilebilir:
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]);
}
}
geri çağrı türünün düz, çıkarılması gereken şablonlu bir argüman olması için daha genel (ve en kolay) sözdizimiyle:
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]);
}
}
Not: Birlikte verilen çıktı şablonlanmış tip için çıkarılan tip adını yazdırır F
. Uygulaması type_name
bu yazının sonunda verilmiştir.
Bir aralığın tekli dönüşümü için en genel uygulama, standart kütüphanenin bir parçasıdır, yani std::transform
yinelenen türlere göre ayarlanmaktadır.
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;
}
Şablonlu std::function
geri arama yöntemi için uyumlu tipler stdf_transform_every_int_templ
, yukarıda belirtilen tiplerle aynıdır (bakınız 3.4).
Bununla birlikte, şablon sürümü kullanıldığında, kullanılan geri aramanın imzası biraz değişebilir:
// 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);
Not: std_ftransform_every_int
(şablonlanmamış sürüm; yukarıya bakın) ile çalışır foo
ancak kullanılmaz 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";
}
Düz şablonlanmış parametre, transform_every_int_templ
olası her çağrılabilir tip olabilir.
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);
Yukarıdaki kod yazdırılır:
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
yukarıda kullanılan uygulama#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
, bu bir yazım hatası değil mi? foo
AFAIK'ın çalışması için bir işaretçi olmalıdır.
[conv.func]
C ++ 11 standardından şöyle diyor: " T fonksiyon tipinin bir değeri," işaretçi T "tipinde bir ön değere dönüştürülebilir. Sonuç işlevin bir göstergesidir. "Bu standart bir dönüşümdür ve dolaylı olarak gerçekleşir. Buradaki işlev işaretçisini (elbette) kullanabilirsiniz.
Geri arama yapmanın C yolu da vardır: işlev işaretçileri
//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);
}
Şimdi sınıf yöntemlerini geri çağrı olarak iletmek istiyorsanız, bu işlev işaretleyicilerinin bildirimlerinin daha karmaşık bildirimleri vardır, örnek:
//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
Geri arama türünü girmeden nasıl yapabilirim ? Hatta mümkün mü?
typedef
daha okunaklı hale getirmek için sadece sözdizimsel şekerdir. Olmadan typedef
, işlev işaretçileri için DoWorkObject tanımı şöyle olacaktır: void DoWorkObject(int (*callback)(float))
. Üye işaretçiler için:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers güzel bir örnek veriyor:
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;
};
Bence örnek her şeyi söylüyor.
std::function<>
C ++ geri aramalarını yazmanın "modern" yoludur.
Bir geri arama işlevi geçirilen edildiği rutini tarafından bir noktada bir rutine geçirildi ve adlandırılan bir yöntemdir.
Bu, yeniden kullanılabilir yazılım yapmak için çok yararlıdır. Örneğin, birçok işletim sistemi API'sı (Windows API gibi) geri aramaları yoğun olarak kullanır.
Örneğin, bir klasördeki dosyalarla çalışmak istiyorsanız - kendi rutininizle bir API işlevi çağırabilirsiniz ve rutininiz belirtilen klasördeki her dosya için bir kez çalıştırılır. Bu API'nın çok esnek olmasını sağlar.
Kabul edilen cevap çok faydalı ve oldukça kapsamlı. Ancak, OP belirtiyor
Geri arama işlevi yazmak için basit bir örnek görmek istiyorum .
İşte burada, C ++ 11'den std::function
fonksiyon işaretçileri ve benzeri şeylere gerek yok:
#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;
}
Bu örnek bir şekilde gerçek, çünkü print_hashes
hash fonksiyonlarının farklı uygulamaları ile fonksiyon çağırmak istiyorsunuz , bu amaçla basit bir tane sağladım. Bir dize alır, bir int (sağlanan dizenin karma değeri) döndürür ve sözdizimi bölümünden hatırlamanız gereken tek şey std::function<int (const std::string&)>
, bu işlevi, onu çağıracak işlevin girdi argümanı olarak tanımlayan işlevdir.
C ++ 'da açık bir geri çağırma işlevi kavramı yoktur. Geri arama mekanizmaları genellikle işlev işaretçileri, işlev nesneleri veya geri arama nesneleri aracılığıyla uygulanır. Programcılar açıkça geri arama işlevselliği tasarlamalı ve uygulamalıdır.
Geri bildirime göre düzenleme:
Bu cevabın aldığı olumsuz geri bildirime rağmen yanlış değil. Nereden geldiğimi açıklamak için daha iyi bir iş yapmaya çalışacağım.
C ve C ++ geri arama işlevlerini uygulamak için ihtiyacınız olan her şeye sahiptir. Geri arama işlevi uygulamanın en yaygın ve önemsiz yolu, işlev işaretçisi işlev bağımsız değişkeni olarak iletilmesidir.
Ancak, geri arama işlevleri ve işlev işaretçileri eş anlamlı değildir. İşlev işaretçisi bir dil mekanizması iken geri arama işlevi semantik bir kavramdır. İşlev işaretçileri bir geri çağırma işlevi gerçekleştirmenin tek yolu değildir; ayrıca işlevler ve hatta bahçe çeşitliliği sanal işlevleri de kullanabilirsiniz. Bir işlevi geri arama yapan şey, işlevi tanımlamak ve çağırmak için kullanılan mekanizma değil, aramanın bağlamı ve anlamsallığıdır. Bir şey geri çağırma işlevi olduğunu söylemek, çağrı işlevi ile çağrılan özel işlev arasında normalden daha büyük bir ayırma anlamına gelir; arayan ve arayan kişi arasında daha gevşek bir kavramsal bağlantı ve arayan, çağrılan şey üzerinde açık kontrole sahiptir.
Örneğin, IFormatProvider için .NET belgeleri , yalnızca bir çalışma ortamı arabirim yöntemi olsa da, "GetFormat bir geri arama yöntemidir" diyor . Kimsenin tüm sanal yöntem çağrılarının geri çağrı işlevleri olduğunu iddia edeceğini sanmıyorum. GetFormat'ı geri arama yöntemi yapan şey, nasıl iletildiğini veya çağrıldığının mekaniği değil, arayan kişinin hangi nesnenin GetFormat yönteminin çağrıldığını seçmesinin anlambilimidir.
Bazı diller, genellikle olaylar ve olay işleme ile ilişkili açık geri arama semantiği olan özellikler içerir. Örneğin, C # sözdizimi ve semantik ile geri çağrı kavramı etrafında açıkça tasarlanmış olay türüne sahiptir. Visual Basic, delege veya işlev işaretçisi kavramını soyutlarken bir geri çağırma işlevi olarak açıkça bir yöntem bildiren kendi Handles yan tümcesi vardır . Bu durumlarda, semantik geri arama kavramı dilin kendisine entegre edilir.
C ve C ++ ise, geri arama işlevlerinin semantik kavramını neredeyse açık bir şekilde gömmez . Mekanizmalar orada, entegre anlambilim de yok. Geri arama işlevlerini gayet iyi uygulayabilirsiniz, ancak açık geri çağrı semantiği içeren daha karmaşık bir şey elde etmek için, C ++'ın Sinyalleri ve Yuvaları ile ne yaptığı gibi C ++'ın sağladığı şeylerin üzerine inşa etmeniz gerekir .
Özetle, C ++, genellikle kolayca ve önemsiz bir şekilde işlev işaretçileri kullanarak geri çağrıları uygulamak için ihtiyacınız olan her şeye sahiptir. Semantikleri yükselt , yay , Kollar , olay + = , vb. Gibi geri çağrılara özgü anahtar kelimeler ve özelliklerdir . Bu tür öğelere sahip bir dilden geliyorsanız, C ++ 'da yerel geri arama desteği kısır hissedecek.
Geri arama işlevleri C standardının bir parçasıdır, bu nedenle C ++ 'ın bir parçasıdır. Ancak C ++ ile çalışıyorsanız, bunun yerine gözlemci desenini kullanmanızı öneririm : http://en.wikipedia.org/wiki/Observer_pattern
Geri arama işlevinin başka bir işleve iletildiğini ve bir noktada çağrıldığını belirten yukarıdaki tanıma bakın.
C ++ 'da geri çağırma işlevlerinin bir sınıflar yöntemi çağırması istenir. Bunu yaptığınızda üye verilerine erişebilirsiniz. Bir geri çağırma tanımlamak için C yöntemini kullanırsanız, bunu statik bir üye işlevine yönlendirmeniz gerekir. Bu çok arzu edilen bir durum değil.
C ++ 'da geri çağrıları nasıl kullanabileceğiniz aşağıda açıklanmıştır. 4 dosya varsayın. Her sınıf için bir çift .CPP / .H dosyası. C1 Sınıfı, geri çağırmak istediğimiz yöntemi içeren sınıftır. C2, C1'in yöntemini geri çağırır. Bu örnekte, geri arama işlevi, okuyucu uğruna eklediğim 1 parametreyi alır. Örnek, somutlaştırılan ve kullanılan hiçbir nesneyi göstermez. Bu uygulama için bir kullanım örneği, verileri okuyan ve geçici alana depolayan bir sınıfınız ve verileri işleyen bir sınıfınız olduğundadır. Bir geri arama fonksiyonu ile, her veri satırı için geri aramayı okuyabilir. Bu teknik, gerekli geçici alanın ek yükünü keser. Özellikle sonradan işlenmesi gereken büyük miktarda veri döndüren SQL sorguları için kullanışlıdır.
/////////////////////////////////////////////////////////////////////
// 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);
}
Boost signals2 sen (! Şablonlar olmadan) ve evre şekilde jenerik üye işlevlerini abone olmalarını sağlar.
Örnek: Belge Görünümü Sinyalleri, esnek Belge Görünümü mimarilerini uygulamak için kullanılabilir. Belge, görünümlerin her birinin bağlanabileceği bir sinyal içerecektir. Aşağıdaki Document sınıfı, çoklu görünümleri destekleyen basit bir metin belgesi tanımlar. Tüm görünümlerin bağlanacağı tek bir sinyal sakladığını unutmayın.
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;
};
Ardından, görünümleri tanımlamaya başlayabiliriz. Aşağıdaki TextView sınıfı, belge metninin basit bir görünümünü sağlar.
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;
};
Kabul edilen cevap kapsamlı ama soru ile ilgili sadece basit bir örnek koymak istiyorum. Uzun zaman önce yazdığım bir kod vardı. sırayla (sol-düğüm sonra kök-düğüm sonra sağ-düğüm) bir ağaç üzerinden geçmek istedim ve ne zaman bir düğüme ulaşmak her şeyi yapabilmek için rasgele bir işlev çağırmak istedim.
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);
}