Odpowiedzi:
Uwaga: Większość odpowiedzi dotyczy wskaźników funkcji, co jest jedną z możliwości uzyskania logiki „wywołania zwrotnego” w C ++, ale jak na razie nie jest to najbardziej korzystna.
Oddzwonienie to wywoływalne (patrz niżej) akceptowane przez klasę lub funkcję, używane do dostosowania bieżącej logiki w zależności od tego wywołania zwrotnego.
Jednym z powodów używania wywołań zwrotnych jest pisanie kodu ogólnego, który jest niezależny od logiki wywoływanej funkcji i może być ponownie użyty z różnymi wywołaniami zwrotnymi.
Wiele funkcji biblioteki standardowych algorytmów <algorithm>
wykorzystuje wywołania zwrotne. Na przykład for_each
algorytm stosuje jednoargumentowe wywołanie zwrotne do każdego elementu w zakresie iteratorów:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
które można wykorzystać do zwiększenia, a następnie wydrukowania wektora, przekazując odpowiednie kallasze, na przykład:
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 << " "; });
który drukuje
5 6.2 8 9.5 11.2
Innym zastosowaniem wywołań zwrotnych jest powiadamianie dzwoniących o określonych zdarzeniach, które umożliwia pewną elastyczność czasu statycznego / kompilacji.
Osobiście korzystam z lokalnej biblioteki optymalizacji, która korzysta z dwóch różnych wywołań zwrotnych:
Tak więc projektant biblioteki nie jest odpowiedzialny za decydowanie o tym, co dzieje się z informacjami przekazywanymi programiście za pośrednictwem wywołania zwrotnego powiadomienia i nie musi się martwić, jak faktycznie określić wartości funkcji, ponieważ są one dostarczane przez wywołanie logiczne. Poprawienie tych rzeczy jest zadaniem użytkownika biblioteki i sprawia, że biblioteka jest wąska i bardziej ogólna.
Ponadto wywołania zwrotne mogą umożliwić zachowanie dynamicznego środowiska wykonawczego.
Wyobraź sobie jakąś klasę silnika gry, która ma uruchamianą funkcję, za każdym razem, gdy użytkownik naciska przycisk na klawiaturze i zestaw funkcji kontrolujących twoje zachowanie w grze. Za pomocą wywołań zwrotnych możesz (ponownie) zdecydować w czasie wykonywania, które działanie zostanie podjęte.
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;
}
};
Tutaj funkcja key_pressed
wykorzystuje wywołania zwrotne zapisane w actions
celu uzyskania pożądanego zachowania po naciśnięciu określonego klawisza. Jeśli gracz zdecyduje się zmienić przycisk skoku, silnik może zadzwonić
game_core_instance.update_keybind(newly_selected_key, &player_jump);
i w ten sposób zmień zachowanie połączenia key_pressed
( do którego dzwoni player_jump
), gdy ten przycisk zostanie naciśnięty następnym razem w grze.
Zobacz pojęcia C ++: na żądanie cppreference, aby uzyskać bardziej formalny opis.
Funkcję oddzwaniania można realizować na kilka sposobów w C ++ (11), ponieważ można wywoływać kilka różnych rzeczy * :
std::function
przedmiotyoperator()
)* Uwaga: Wskaźnik do elementów danych jest również możliwy do wywołania, ale żadna funkcja nie jest w ogóle wywoływana.
Uwaga: Począwszy od C ++ 17, f(...)
można napisać takie wywołanie, std::invoke(f, ...)
które obsługuje również wskaźnik do wielkości elementu.
Wskaźnik funkcji jest „najprostszym” (pod względem ogólności; pod względem czytelności prawdopodobnie najgorszym) typem, który może mieć wywołanie zwrotne.
Miejmy prostą funkcję foo
:
int foo (int x) { return 2+x; }
Typu wskaźnik funkcja ma notacji
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
gdzie będzie wyglądał nazwany typ wskaźnika funkcji
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
Deklaracja daje nam możliwość, aby rzeczy trochę bardziej czytelne, ponieważ typedef
za f_int_t
można również zapisać jako:
using f_int_t = int(*)(int);
Gdzie (przynajmniej dla mnie) jest to wyraźniejsze f_int_t
jest alias nowego typu, a rozpoznanie typu wskaźnika funkcji jest również łatwiejsze
Deklaracja funkcji wykorzystująca wywołanie zwrotne typu wskaźnika funkcji będzie:
// 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);
Notacja wywołania jest zgodna z prostą składnią wywołania funkcji:
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
}
Funkcja zwrotna, która przyjmuje wskaźnik funkcji, może zostać wywołana za pomocą wskaźników funkcji.
Korzystanie z funkcji, która pobiera wywołanie zwrotne wskaźnika funkcji, jest dość proste:
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
Można napisać funkcję, która nie zależy od działania wywołania zwrotnego:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
tam, gdzie to możliwe, mogą być zwrotne
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
używane jak
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};
Wskaźnik do funkcji składowej (jakiejś klasy C
) jest specjalnym typem (a nawet bardziej złożonym) wskaźnika funkcji, który wymaga obiektu typu C
do działania.
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Wskaźnik zarejestrował typu funkcyjnego dla pewnej klasy T
ma notacji
// 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)
gdzie nazwany wskaźnik do funkcji składowej będzie wyglądał analogicznie do wskaźnika funkcji:
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;
Przykład: Zadeklarowanie funkcji przyjmującej wskaźnik do wywołania zwrotnego funkcji członka jako jeden z jej argumentów:
// 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);
Funkcję wskaźnika do elementu członkowskiego C
można wywołać w odniesieniu do obiektu typu C
za pomocą operacji dostępu do elementu na zdereferencyjnym wskaźniku.
Uwaga: wymagany nawias!
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
}
Uwaga: Jeśli wskaźnik do C
jest dostępny, jego składnia jest równoważna (gdzie wskaźnik do również C
musi zostać usunięty):
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);
}
Funkcja zwrotna przyjmująca wskaźnik funkcji składowej klasy T
może być wywoływana za pomocą wskaźnika funkcji składowej klasy T
.
Użycie funkcji, która przenosi wskaźnik do wywołania zwrotnego funkcji elementu, jest - podobnie jak wskaźniki funkcji - całkiem proste:
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
obiekty (nagłówek<functional>
)std::function
Klasa jest polimorficzny funkcja otoki do przechowywania, kopiowania lub invoke callables.
std::function
notacji obiektowej / typuTyp std::function
obiektu przechowującego wywołanie wygląda następująco:
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;
Klasa std::function
jest operator()
zdefiniowana, które mogą być wykorzystane do wywołania swój cel.
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
Zwrotna jest bardziej ogólne niż wskaźników funkcji lub wskaźnik do funkcji składowej ponieważ różne typy mogą być przekazywane i niejawnie przekształcony w std::function
obiekt.
3.3.1 Wskaźniki funkcji i wskaźniki funkcji składowych
Wskaźnik funkcji
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
lub wskaźnik do funkcji składowej
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
może być użyty.
3.3.2 Wyrażenia lambda
Nienazwane zamknięcie wyrażenia lambda może być przechowywane w std::function
obiekcie:
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
wyrażenia
Wynik std::bind
wyrażenia można przekazać. Na przykład przez powiązanie parametrów z wywołaniem wskaźnika funkcji:
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 )
Gdzie również obiekty mogą być powiązane jako obiekt do wywołania wskaźnika do funkcji składowych:
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 Obiekty funkcyjne
Obiekty klas o odpowiednim operator()
przeciążeniu mogą być również przechowywane wewnątrz std::function
obiektu.
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 )
Zmiana przykładowego wskaźnika funkcji do użycia 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]);
}
}
daje o wiele większą użyteczność tej funkcji, ponieważ (patrz 3.3) mamy więcej możliwości korzystania z niej:
// 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};
Za pomocą szablonów kod wywołujący wywołanie zwrotne może być nawet bardziej ogólny niż używanie std::function
obiektów.
Należy pamiętać, że szablony są funkcją czasu kompilacji i są narzędziem do projektowania polimorfizmu w czasie kompilacji. Jeśli zachowanie dynamiczne w środowisku wykonawczym ma zostać osiągnięte poprzez wywołania zwrotne, szablony pomogą, ale nie wywołają dynamiki środowiska wykonawczego.
Uogólnienie, tj. std_ftransform_every_int
Kod z góry jeszcze bardziej, można osiągnąć za pomocą szablonów:
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]);
}
}
z jeszcze bardziej ogólną (jak również najłatwiejszą) składnią dla typu wywołania zwrotnego będącego prostym argumentem szablonowym:
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]);
}
}
Uwaga: Dołączone dane wyjściowe wypisują nazwę typu wydedukowaną dla typu szablonowego F
. Realizacja type_name
jest podana na końcu tego postu.
Najbardziej ogólna implementacja jednoargumentowej transformacji zakresu jest częścią standardowej biblioteki, a mianowicie std::transform
, która jest również wzorowana w odniesieniu do typów iterowanych.
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;
}
Typy kompatybilne dla matrycowej std::function
metody wywołania zwrotnego stdf_transform_every_int_templ
są identyczne z wyżej wymienionymi typami (patrz 3.4).
Jednak w wersji z szablonem podpis używanego wywołania zwrotnego może się nieco zmienić:
// 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);
Uwaga: std_ftransform_every_int
(wersja bez szablonów; patrz wyżej) działa, foo
ale nie używa 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";
}
Prostym parametrem szablonu transform_every_int_templ
może być każdy możliwy typ wywoływalny.
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);
Powyższy kod drukuje:
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
implementacja zastosowana powyżej#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
, to jest literówka, prawda? foo
powinien być wskaźnikiem, aby działało AFAIK.
[conv.func]
standardu C ++ 11 mówi: „ Wartość funkcji typu T można przekonwertować na wartość typu„ wskaźnik na T. ” Wynikiem jest wskaźnik do funkcji. ”Jest to standardowa konwersja i jako taka zachodzi niejawnie. Można tutaj (oczywiście) użyć wskaźnika funkcji.
Istnieje również sposób wywołania zwrotnego w języku C: wskaźniki funkcji
//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);
}
Teraz, jeśli chcesz przekazać metody klas jako wywołania zwrotne, deklaracje do tych wskaźników funkcji mają bardziej złożone deklaracje, na przykład:
//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
wprowadzania typu wywołania zwrotnego? Czy to w ogóle możliwe?
typedef
to tylko cukier składniowy, aby był bardziej czytelny. Bez typedef
definicja DoWorkObject dla wskaźników funkcji będzie: void DoWorkObject(int (*callback)(float))
. Wskaźniki członków to:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers podaje ładny przykład:
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;
};
Myślę, że przykład mówi wszystko.
std::function<>
to „nowoczesny” sposób pisania wywołań zwrotnych C ++.
Funkcja zwrotna to metoda przekazywana do procedury i wywoływana w pewnym momencie przez procedurę, do której jest przekazywana.
Jest to bardzo przydatne do tworzenia oprogramowania wielokrotnego użytku. Na przykład wiele interfejsów API systemu operacyjnego (takich jak Windows API) intensywnie wykorzystuje wywołania zwrotne.
Na przykład, jeśli chcesz pracować z plikami w folderze - możesz wywołać funkcję API z własną procedurą, a twoja procedura uruchamia się raz na plik w określonym folderze. Dzięki temu interfejs API jest bardzo elastyczny.
Przyjęta odpowiedź jest bardzo przydatna i dość wyczerpująca. Jednak OP stwierdza
Chciałbym zobaczyć prosty przykład napisania funkcji zwrotnej.
Więc proszę, od C ++ 11 masz, std::function
więc nie ma potrzeby używania wskaźników funkcji i podobnych rzeczy:
#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;
}
Ten przykład jest jakoś prawdziwy, ponieważ chcesz wywołać funkcję print_hashes
z różnymi implementacjami funkcji skrótu, w tym celu podałem prostą. Odbiera ciąg, zwraca wartość int (wartość skrótu podanego ciągu) i wszystko, co musisz zapamiętać z części składni, to std::function<int (const std::string&)>
opisanie takiej funkcji jako argument wejściowy funkcji, która ją wywoła.
W C ++ nie ma wyraźnej koncepcji funkcji zwrotnej. Mechanizmy zwrotne są często implementowane za pomocą wskaźników funkcji, obiektów funktorów lub obiektów zwrotnych. Programiści muszą jawnie zaprojektować i wdrożyć funkcjonalność wywołania zwrotnego.
Edytuj na podstawie opinii:
Pomimo negatywnej opinii, którą otrzymała ta odpowiedź, nie jest zła. Spróbuję lepiej wyjaśnić, skąd pochodzę.
C i C ++ mają wszystko, czego potrzebujesz, aby zaimplementować funkcje zwrotne. Najczęstszym i najprostszym sposobem implementacji funkcji zwrotnej jest przekazanie wskaźnika funkcji jako argumentu funkcji.
Jednak funkcje oddzwaniania i wskaźniki funkcji nie są synonimami. Wskaźnik funkcji jest mechanizmem językowym, a funkcja zwrotna jest pojęciem semantycznym. Wskaźniki funkcji nie są jedynym sposobem na wdrożenie funkcji wywołania zwrotnego - możesz także użyć funktorów, a nawet funkcji wirtualnych odmiany ogrodu. To, co powoduje, że wywołanie funkcji jest wywołaniem zwrotnym, to nie mechanizm używany do identyfikacji i wywoływania funkcji, ale kontekst i semantyka wywołania. Powiedzenie czegoś jest funkcją wywołania zwrotnego oznacza większą niż normalnie separację między funkcją wywołującą a wywoływaną określoną funkcją, luźniejsze połączenie koncepcyjne między dzwoniącym a odbiorcą, przy czym dzwoniący ma wyraźną kontrolę nad tym, co zostanie wywołane.
Na przykład dokumentacja .NET dla IFormatProvider mówi, że „GetFormat jest metodą wywołania zwrotnego” , nawet jeśli jest to zwykła metoda interfejsu. Nie sądzę, aby ktokolwiek argumentował, że wszystkie wirtualne wywołania metod są funkcjami zwrotnymi. To, co sprawia, że GetFormat jest metodą wywołania zwrotnego, to nie mechanika tego, w jaki sposób jest ona przekazywana lub wywoływana, ale semantyka wybierania numeru wywołującego, która metoda GetFormat obiektu zostanie wywołana.
Niektóre języki zawierają funkcje z wyraźną semantyką wywołania zwrotnego, zwykle związane ze zdarzeniami i obsługą zdarzeń. Na przykład C # ma typ zdarzenia ze składnią i semantyką wyraźnie zaprojektowaną wokół koncepcji wywołań zwrotnych. Visual Basic ma klauzulę Handles , która jawnie deklaruje metodę jako funkcję zwrotną, jednocześnie abstrahując od koncepcji delegatów lub wskaźników funkcji. W takich przypadkach semantyczna koncepcja wywołania zwrotnego jest zintegrowana z samym językiem.
Z kolei C i C ++ nie osadzają semantycznej koncepcji funkcji zwrotnych prawie tak wyraźnie. Istnieją mechanizmy, nie ma zintegrowanej semantyki. Możesz dobrze zaimplementować funkcje zwrotne, ale aby uzyskać coś bardziej zaawansowanego, w tym wyraźną semantykę zwrotną, musisz zbudować ją na podstawie tego, co zapewnia C ++, na przykład tego, co Qt zrobił z ich sygnałami i gniazdami .
W skrócie, C ++ ma to, czego potrzebujesz do implementacji wywołań zwrotnych, często dość łatwo i trywialnie za pomocą wskaźników funkcji. To, czego nie ma, to słowa kluczowe i funkcje, których semantyka jest specyficzna dla wywołań zwrotnych, takich jak podbicie , emisja , uchwyty , zdarzenie + = itp. Jeśli pochodzisz z języka zawierającego takie typy elementów, natywne wsparcie zwrotne w C ++ będzie się czuł neutralny.
Funkcje zwrotne są częścią standardu C, a zatem również częścią C ++. Ale jeśli pracujesz z C ++, sugerowałbym zamiast tego użyć wzorca obserwatora : http://en.wikipedia.org/wiki/Observer_pattern
Zobacz powyższą definicję, w której stwierdzono, że funkcja zwrotna jest przekazywana do innej funkcji i w pewnym momencie jest wywoływana.
W C ++ pożądane jest, aby funkcje zwrotne wywoływały metodę klas. Po wykonaniu tej czynności masz dostęp do danych członka. Jeśli użyjesz C do zdefiniowania wywołania zwrotnego, będziesz musiał skierować go do statycznej funkcji składowej. To nie jest bardzo pożądane.
Oto, w jaki sposób można korzystać z wywołań zwrotnych w C ++. Załóż 4 pliki. Para plików .CPP / .H dla każdej klasy. Klasa C1 to klasa z metodą, którą chcemy oddzwonić. C2 odwołuje się do metody C1. W tym przykładzie funkcja zwrotna przyjmuje 1 parametr, który dodałem dla czytelników. W przykładzie nie pokazano żadnych obiektów, które są tworzone i wykorzystywane. Jednym z przypadków użycia tej implementacji jest sytuacja, gdy masz jedną klasę, która odczytuje i przechowuje dane w tymczasowej przestrzeni, a drugą, która publikuje dane. Dzięki funkcji wywołania zwrotnego, dla każdego odczytanego wiersza danych, wywołanie zwrotne może następnie przetworzyć. Ta technika odcina narzut wymaganej tymczasowej przestrzeni. Jest to szczególnie przydatne w przypadku zapytań SQL, które zwracają dużą ilość danych, które następnie muszą zostać przetworzone.
/////////////////////////////////////////////////////////////////////
// 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 za signals2 pozwala subskrybować rodzajowe funkcji składowych (bez szablonów!) Iw sposób THREADSAFE.
Przykład: Sygnały widoku dokumentu można wykorzystać do implementacji elastycznych architektur widoku dokumentu. Dokument będzie zawierał sygnał, z którym każdy widok może się połączyć. Następująca klasa Document definiuje prosty dokument tekstowy, który obsługuje wiele widoków. Zauważ, że przechowuje pojedynczy sygnał, do którego zostaną podłączone wszystkie widoki.
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;
};
Następnie możemy zacząć definiować widoki. Następująca klasa TextView zapewnia prosty widok tekstu dokumentu.
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;
};
Przyjęta odpowiedź jest wyczerpująca, ale związana z pytaniem, chcę tylko tutaj podać prosty przykład. Miałem kod, który napisałem dawno temu. chciałem przemierzać drzewo w uporządkowany sposób (lewy węzeł, następnie węzeł główny, a następnie prawy) i za każdym razem, gdy docieram do jednego węzła, chciałem móc wywołać dowolną funkcję, aby mogła zrobić wszystko.
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);
}