[To] ( thispointer.com/… ) bardzo dobrze wyjaśnia podstawy funkcji oddzwaniania i łatwo ją zrozumieć.
Anurag Singh
Odpowiedzi:
449
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.
Co to są callbacki (?) I dlaczego ich używać (!)
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_eachalgorytm stosuje jednoargumentowe wywołanie zwrotne do każdego elementu w zakresie iteratorów:
template<classInputIt,classUnaryFunction>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
56.289.511.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:
Pierwsze wywołanie zwrotne jest wywoływane, jeśli wymagana jest wartość funkcji i gradient oparty na wektorze wartości wejściowych (wywołanie logiczne: wyznaczanie wartości funkcji / wyprowadzanie gradientu).
Drugie wywołanie zwrotne jest wywoływane raz dla każdego kroku algorytmu i odbiera pewne informacje o zbieżności algorytmu (wywołanie zwrotne powiadomienia).
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.
Tutaj funkcja key_pressedwykorzystuje wywołania zwrotne zapisane w actionscelu uzyskania pożądanego zachowania po naciśnięciu określonego klawisza. Jeśli gracz zdecyduje się zmienić przycisk skoku, silnik może zadzwonić
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.
Co można wywoływać w C ++ (11)?
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 * :
Wskaźniki funkcji (w tym wskaźniki funkcji składowych)
std::function przedmioty
Wyrażenia lambda
Wiąż wyrażenia
Obiekty funkcji (klasy z przeciążonym operatorem wywołania funkcji operator())
* 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.
Kilka ważnych sposobów szczegółowego zapisywania wywołań zwrotnych
X.1 „Zapisywanie” wywołania zwrotnego w tym poście oznacza składnię do deklarowania i nazwania typu wywołania zwrotnego.
X.2 „Wywołanie” wywołanie zwrotne odnosi się do składni wywoływania tych obiektów.
X.3 „Używanie” wywołania zwrotnego oznacza składnię podczas przekazywania argumentów do funkcji za pomocą wywołania zwrotnego.
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.
1. Wskaźniki funkcji
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){return2+x;}
1.1 Pisanie notacji wskaźnik / typ funkcji
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 inttypedefint(*f_int_t)(int);// foo_p is a pointer to function taking int returning int// initialized by pointer to function foo taking int returning intint(* foo_p)(int)=&foo;// can alternatively be written as f_int_t foo_p =&foo;
usingDeklaracja daje nam możliwość, aby rzeczy trochę bardziej czytelne, ponieważ typedefza f_int_tmożna również zapisać jako:
usingf_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 argumentint 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);
1.2 Notacja połączeń oddzwaniania
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}// analogint foobar (int x,f_int_t moo){return x + moo(x);// function pointer moo called using argument x}
1.3 Notacja dotycząca oddzwaniania i kompatybilne typy
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 beint b = foobar(a,&foo);// call foobar with pointer to foo as callback
1.4 Przykład
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){return2*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};
2. Wskaźnik do funkcji składowej
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 Cdo działania.
struct C
{int y;int foo(int x)const{return x+y;}};
2.1 Pisanie wskaźnika do notacji funkcji / typu elementu
Wskaźnik zarejestrował typu funkcyjnego dla pewnej klasy Tma 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 typeint(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:typedefint(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 Cint(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 cint 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);
2.2 Notacja połączeń oddzwaniania
Funkcję wskaźnika do elementu członkowskiego Cmożna wywołać w odniesieniu do obiektu typu Cza 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}// analogint 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 Cjest dostępny, jego składnia jest równoważna (gdzie wskaźnik do również Cmusi 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 xreturn 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 xreturn x +(c->*meow)(x);}
2.3 Notacja użycia oddzwaniania i kompatybilne typy
Funkcja zwrotna przyjmująca wskaźnik funkcji składowej klasy Tmoż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 initializationint a =5;int b = C_foobar(a, my_c,&C::foo);// call C_foobar with pointer to foo as its callback
3. std::functionobiekty (nagłówek<functional> )
std::functionKlasa jest polimorficzny funkcja otoki do przechowywania, kopiowania lub invoke callables.
3.1 Pisanie std::functionnotacji obiektowej / typu
Typ std::functionobiektu 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;
3.2 Notacja połączeń oddzwaniania
Klasa std::functionjest 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}
3.3 Notacja dotycząca oddzwaniania i kompatybilne typy
std::functionZwrotna 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::functionobiekt.
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 initializationint 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::functionobiekcie:
int a =2;int c =3;int b = stdf_foobar(a,[c](int x)->int{return7+c*x;});// b == 15 == a + (7*c*a) == 2 + (7+3*2)
3.3.3 std::bindwyrażenia
Wynik std::bindwyraż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){return9*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 initializationint 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::functionobiektu.
structMeow{int y =0;Meow(int y_): y(y_){}intoperator()(int x){return y * x;}};int a =11;int b = stdf_foobar(a,Meow{8});// b == 99 == 11 + ( 8 * 11 )
3.4 Przykład
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 possibleint 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){return9*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};
4. Szablon zwrotny typu zwrotnego
Za pomocą szablonów kod wywołujący wywołanie zwrotne może być nawet bardziej ogólny niż używanie std::functionobiektó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.
4.1 Pisanie (notacje typów) i wywoływanie zwrotnych szablonów
Uogólnienie, tj. std_ftransform_every_intKod 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,unsignedconst 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,unsignedconst 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_namejest 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.
4.2 Przykłady użycia oddzwaniania szablonowego i kompatybilnych typów
Typy kompatybilne dla matrycowej std::functionmetody wywołania zwrotnego stdf_transform_every_int_templsą 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ć:
// Letint foo (int x){return2+x;}int muh (intconst&x){return3+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,intconst&>(&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, fooale nie używa muh.
// Letvoid print_int(int* p,unsignedconst 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_templmoż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);
#include<type_traits>#include<typeinfo>#include<string>#include<memory>#include<cxxabi.h>template<class T>
std::string type_name(){typedeftypename 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 +=" &";elseif(std::is_rvalue_reference<T>::value)
r +=" &&";return r;}
@BogeyJammer: W przypadku, gdy nie zauważyłeś: Odpowiedź składa się z dwóch części. 1. Ogólne wyjaśnienie „wywołań zwrotnych” z małym przykładem. 2. Pełna lista różnych wywołań i sposobów pisania kodu za pomocą wywołań zwrotnych. Możesz nie zagłębiać się w szczegóły lub przeczytać całą odpowiedź, ale tylko dlatego, że nie chcesz szczegółowego widoku, nie jest tak, że odpowiedź jest nieskuteczna lub „brutalnie skopiowana”. Temat to „wywołania zwrotne c ++”. Nawet jeśli część 1 jest odpowiednia dla OP, inni mogą uznać część 2 za przydatną. Zachęcamy do zwrócenia uwagi na brak informacji lub konstruktywną krytykę pierwszej części zamiast -1.
Pixelchemist
1
Część 1 nie jest wystarczająco przyjazna dla początkujących. Nie mogę być bardziej konstruktywny, mówiąc, że nie udało mi się czegoś nauczyć. A część 2 nie została poproszona o zalanie strony i nie wchodzi w rachubę, nawet jeśli udajesz, że jest przydatna, mimo że często znajduje się w dedykowanej dokumentacji, w której szuka się tak szczegółowych informacji. Zdecydowanie trzymam głos. Jeden głos reprezentuje osobistą opinię, więc proszę ją zaakceptować i uszanować.
Bogey Jammer
24
@BogeyJammer Nie jestem nowy w programowaniu, ale jestem nowy w „modern c ++”. Ta odpowiedź daje mi dokładny kontekst, który muszę uzasadnić na temat roli, jaką odgrywają oddzwaniania, w szczególności w c ++. OP mógł nie poprosić o wiele przykładów, ale zwyczajowo na SO, w niekończącym się dążeniu do edukacji świata głupców, wymienia się wszystkie możliwe rozwiązania pytania. Jeśli brzmi to jak książka, jedyną radą, jaką mogę zaoferować, jest poćwiczyć, czytając kilka z nich .
dcow
int b = foobar(a, foo); // call foobar with pointer to foo as callback, to jest literówka, prawda? foopowinien być wskaźnikiem, aby działało AFAIK.
konoufo
@konoufo: [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.
Pixelchemist,
160
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 inttypedefint(*CallbackType)(float);voidDoWork(CallbackType callback){float variable =0.0f;//Do calculations//Call the callback with the variable, and retrieve the//resultint result = callback(variable);//Do something with the result}intSomeCallback(float variable){int result;//Interpret variablereturn result;}int main(int argc,char** argv){//Pass in SomeCallback to the DoWorkDoWork(&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:typedefint(ClassName::*CallbackType)(float);//This method performs work using an object instancevoidDoWorkObject(CallbackType callback){//Class instance to invoke it throughClassName objectInstance;//Invocationint result =(objectInstance.*callback)(1.0f);}//This method performs work using an object pointervoidDoWorkPointer(CallbackType callback){//Class pointer to invoke it throughClassName* pointerInstance;//Invocationint result =(pointerInstance->*callback)(1.0f);}int main(int argc,char** argv){//Pass in SomeCallback to the DoWorkDoWorkObject(&ClassName::Method);DoWorkPointer(&ClassName::Method);}
Wystąpił błąd w przykładzie metody klasy. Wywołanie powinno być: (instancja. *
Oddzwanianie
Dziękuję za zwrócenie na to uwagi. Dodam oba, aby zilustrować wywoływanie przez obiekt, i poprzez wskaźnik obiektu.
Ramon Zarazua B.
3
Ma to tę wadę, że funkcja std :: tr1: polega na tym, że wywołanie zwrotne jest typowane dla poszczególnych klas; sprawia to, że niepraktyczne jest stosowanie wywołań zwrotnych w stylu C, gdy obiekt wykonujący wywołanie nie zna klasy obiektu, który ma zostać wywołany.
bleater
Jak mogę to zrobić bez typedefwprowadzania typu wywołania zwrotnego? Czy to w ogóle możliwe?
Tomáš Zato - Przywróć Monikę
1
Tak, możesz. typedefto tylko cukier składniowy, aby był bardziej czytelny. Bez typedefdefinicja 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))
Z braku zainteresowania, w której książce SM podaje ten przykład? Pozdrawiam :)
sam-w
5
Wiem, że to jest stare, ale ponieważ prawie zacząłem to robić i ostatecznie nie działało to w mojej konfiguracji (mingw), jeśli używasz GCC w wersji <4.x, ta metoda nie jest obsługiwana. Niektóre zależności, których używam, nie będą się kompilować bez dużego nakładu pracy w wersji gcc> = 4.0.1, więc utknąłem przy użyciu starych, dobrych wywołań zwrotnych w stylu C, które działają dobrze.
OzBarry
38
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.
Ta odpowiedź naprawdę nie oznacza, że programista mówi cokolwiek, czego nie wiedział. Uczę się C ++, znając wiele innych języków. To, co w ogóle jest oddzwanianiem, nie dotyczy mnie.
Tomáš Zato - Przywróć Monikę
17
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::functionwię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;});return0;}
Ten przykład jest jakoś prawdziwy, ponieważ chcesz wywołać funkcję print_hashesz 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.
spośród wszystkich powyższych odpowiedzi ta pozwoliła mi zrozumieć, czym są wywołania zwrotne i jak z nich korzystać. dzięki.
Mehar Charan Sahai
@MeharCharanSahai Cieszę się, że to słyszę :) Nie ma za co.
Miljen Mikic
9
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.
na szczęście nie była to pierwsza odpowiedź, którą przeczytałem, odwiedzając tę stronę, w przeciwnym razie od razu bym się odrzucił!
ubugnu
6
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
Funkcje zwrotne niekoniecznie są synonimami wykonywania funkcji za pomocą wskaźnika funkcji, który został przekazany jako argument. W niektórych definicjach termin funkcja zwrotna przenosi dodatkową semantykę powiadamiania jakiegoś innego kodu o czymś, co właśnie się wydarzyło, lub jest to czas, kiedy coś powinno się wydarzyć. Z tej perspektywy funkcja wywołania zwrotnego nie jest częścią standardu C, ale może być łatwo zaimplementowana za pomocą wskaźników funkcji, które są częścią standardu.
Darryl,
3
„część standardu C, a zatem także część C ++.” Jest to typowe nieporozumienie, ale jednak nieporozumienie :-)
Ograniczone Zadośćuczynienie
Muszę się zgodzić. Zostawię go takim, jakim jest, ponieważ spowoduje to więcej zamieszania, jeśli teraz go zmienię. Chciałem powiedzieć, że wskaźnik funkcji (!) Jest częścią standardu. Mówienie czegoś innego - zgadzam się - jest mylące.
AudioDroid
W jaki sposób funkcje zwrotne są „częścią standardu C”? Nie sądzę, że fakt, że obsługuje funkcje i wskaźniki do funkcji, oznacza, że w szczególności kanonizuje wywołania zwrotne jako koncepcję językową. Poza tym, jak wspomniano, nie byłoby to bezpośrednio związane z C ++, nawet gdyby było dokładne. Jest to szczególnie nieistotne, gdy OP zapytał „kiedy i jak” używać wywołań zwrotnych w C ++ (kiepskie, zbyt szerokie pytanie, ale mimo to), a twoja odpowiedź to tylko napomnienie, aby zrobić coś innego.
underscore_d
4
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 fileclass C1
{public:
C1(){};~C1(){};void CALLBACK F1(int i);};/////////////////////////////////////////////////////////////////////// C1 CPP filevoid CALLBACK C1::F1(int i){// Do stuff with C1, its methods and data, and even do stuff with the passed in parameter}/////////////////////////////////////////////////////////////////////// C2 H Fileclass C1;// Forward declarationclass C2
{typedefvoid(CALLBACK C1::* pfnCallBack)(int i);public:
C2(){};~C2(){};voidFn(C1 * pThat,pfnCallBack pFn);};/////////////////////////////////////////////////////////////////////// C2 CPP Filevoid C2::Fn(C1 * pThat,pfnCallBack pFn){// Call a non-static method in C1int 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.
classDocument{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(constsignal_t::slot_type &subscriber){return m_sig.connect(subscriber);}void append(constchar* 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.
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);}
wiesz, że zaakceptowana odpowiedź jest poprawna i wyczerpująca i myślę, że ogólnie nie ma już nic do powiedzenia. ale zamieszczam przykład mojego użycia funkcji zwrotnych.
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.
Co to są callbacki (?) I dlaczego ich używać (!)
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ładfor_each
algorytm stosuje jednoargumentowe wywołanie zwrotne do każdego elementu w zakresie iteratorów:które można wykorzystać do zwiększenia, a następnie wydrukowania wektora, przekazując odpowiednie kallasze, na przykład:
który drukuje
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.
Tutaj funkcja
key_pressed
wykorzystuje wywołania zwrotne zapisane wactions
celu uzyskania pożądanego zachowania po naciśnięciu określonego klawisza. Jeśli gracz zdecyduje się zmienić przycisk skoku, silnik może zadzwonići w ten sposób zmień zachowanie połączenia
key_pressed
( do którego dzwoniplayer_jump
), gdy ten przycisk zostanie naciśnięty następnym razem w grze.Co można wywoływać w C ++ (11)?
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.
Kilka ważnych sposobów szczegółowego zapisywania wywołań zwrotnych
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.1. Wskaźniki funkcji
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
:1.1 Pisanie notacji wskaźnik / typ funkcji
Typu wskaźnik funkcja ma notacji
gdzie będzie wyglądał nazwany typ wskaźnika funkcji
using
Deklaracja daje nam możliwość, aby rzeczy trochę bardziej czytelne, ponieważtypedef
zaf_int_t
można również zapisać jako:Gdzie (przynajmniej dla mnie) jest to wyraźniejsze
f_int_t
jest alias nowego typu, a rozpoznanie typu wskaźnika funkcji jest również łatwiejszeDeklaracja funkcji wykorzystująca wywołanie zwrotne typu wskaźnika funkcji będzie:
1.2 Notacja połączeń oddzwaniania
Notacja wywołania jest zgodna z prostą składnią wywołania funkcji:
1.3 Notacja dotycząca oddzwaniania i kompatybilne typy
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:
1.4 Przykład
Można napisać funkcję, która nie zależy od działania wywołania zwrotnego:
tam, gdzie to możliwe, mogą być zwrotne
używane jak
2. Wskaźnik do funkcji składowej
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 typuC
do działania.2.1 Pisanie wskaźnika do notacji funkcji / typu elementu
Wskaźnik zarejestrował typu funkcyjnego dla pewnej klasy
T
ma notacjigdzie nazwany wskaźnik do funkcji składowej będzie wyglądał analogicznie do wskaźnika funkcji:
Przykład: Zadeklarowanie funkcji przyjmującej wskaźnik do wywołania zwrotnego funkcji członka jako jeden z jej argumentów:
2.2 Notacja połączeń oddzwaniania
Funkcję wskaźnika do elementu członkowskiego
C
można wywołać w odniesieniu do obiektu typuC
za pomocą operacji dostępu do elementu na zdereferencyjnym wskaźniku. Uwaga: wymagany nawias!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):2.3 Notacja użycia oddzwaniania i kompatybilne typy
Funkcja zwrotna przyjmująca wskaźnik funkcji składowej klasy
T
może być wywoływana za pomocą wskaźnika funkcji składowej klasyT
.Użycie funkcji, która przenosi wskaźnik do wywołania zwrotnego funkcji elementu, jest - podobnie jak wskaźniki funkcji - całkiem proste:
3.
std::function
obiekty (nagłówek<functional>
)std::function
Klasa jest polimorficzny funkcja otoki do przechowywania, kopiowania lub invoke callables.3.1 Pisanie
std::function
notacji obiektowej / typuTyp
std::function
obiektu przechowującego wywołanie wygląda następująco:3.2 Notacja połączeń oddzwaniania
Klasa
std::function
jestoperator()
zdefiniowana, które mogą być wykorzystane do wywołania swój cel.3.3 Notacja dotycząca oddzwaniania i kompatybilne typy
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 wstd::function
obiekt.3.3.1 Wskaźniki funkcji i wskaźniki funkcji składowych
Wskaźnik funkcji
lub wskaźnik do funkcji składowej
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:3.3.3
std::bind
wyrażeniaWynik
std::bind
wyrażenia można przekazać. Na przykład przez powiązanie parametrów z wywołaniem wskaźnika funkcji:Gdzie również obiekty mogą być powiązane jako obiekt do wywołania wskaźnika do funkcji składowych:
3.3.4 Obiekty funkcyjne
Obiekty klas o odpowiednim
operator()
przeciążeniu mogą być również przechowywane wewnątrzstd::function
obiektu.3.4 Przykład
Zmiana przykładowego wskaźnika funkcji do użycia
std::function
daje o wiele większą użyteczność tej funkcji, ponieważ (patrz 3.3) mamy więcej możliwości korzystania z niej:
4. Szablon zwrotny typu zwrotnego
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.
4.1 Pisanie (notacje typów) i wywoływanie zwrotnych szablonów
Uogólnienie, tj.
std_ftransform_every_int
Kod z góry jeszcze bardziej, można osiągnąć za pomocą szablonów:z jeszcze bardziej ogólną (jak również najłatwiejszą) składnią dla typu wywołania zwrotnego będącego prostym argumentem szablonowym:
Uwaga: Dołączone dane wyjściowe wypisują nazwę typu wydedukowaną dla typu szablonowego
F
. Realizacjatype_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.4.2 Przykłady użycia oddzwaniania szablonowego i kompatybilnych typów
Typy kompatybilne dla matrycowej
std::function
metody wywołania zwrotnegostdf_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ć:
Uwaga:
std_ftransform_every_int
(wersja bez szablonów; patrz wyżej) działa,foo
ale nie używamuh
.Prostym parametrem szablonu
transform_every_int_templ
może być każdy możliwy typ wywoływalny.Powyższy kod drukuje:
type_name
implementacja zastosowana powyżejźródło
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
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:
źródło
typedef
wprowadzania typu wywołania zwrotnego? Czy to w ogóle możliwe?typedef
to tylko cukier składniowy, aby był bardziej czytelny. Beztypedef
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:
Myślę, że przykład mówi wszystko.
std::function<>
to „nowoczesny” sposób pisania wywołań zwrotnych C ++.źródło
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.
źródło
Przyjęta odpowiedź jest bardzo przydatna i dość wyczerpująca. Jednak OP stwierdza
Więc proszę, od C ++ 11 masz,
std::function
więc nie ma potrzeby używania wskaźników funkcji i podobnych rzeczy: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, tostd::function<int (const std::string&)>
opisanie takiej funkcji jako argument wejściowy funkcji, która ją wywoła.źródło
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.
źródło
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
źródło
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.
źródło
Boost za signals2 pozwala subskrybować rodzajowe funkcji składowych (bez szablonów!) Iw sposób THREADSAFE.
źródło
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.
źródło