Funkcje zwrotne w c++
W c++, kiedy i jak używać funkcji callback?
EDIT:
Chciałbym zobaczyć prosty przykład do napisania funkcji zwrotnej.
9 answers
Uwaga: Większość odpowiedzi obejmuje Wskaźniki funkcji, które są jedną z możliwości osiągnięcia logiki" callback " w C++, ale na dzień dzisiejszy nie jest to najkorzystniejsza moim zdaniem.
Co to są callbacks (?) i dlaczego ich używać (!)
Wywołanie zwrotne jest wywoływalne (patrz dalej w dół) akceptowane przez klasę lub funkcję, używane do dostosowywania bieżącej logiki w zależności od tego wywołania zwrotnego.
Jednym z powodów użycia callbacks jest napisanie generic kodu, który jest niezależny od logiki w wywołanej funkcji i może być ponownie użyty z różnymi wywołaniami zwrotnymi.
Wiele funkcji standardowej biblioteki algorytmów <algorithm>
używa wywołań zwrotnych. Na przykład algorytm for_each
stosuje jednoargumentowe wywołanie zwrotne do każdego elementu z zakresu 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óry może być użyty do przyrostu, a następnie wydruku wektora, przekazując odpowiednie wywołania, 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óre drukuje
5 6.2 8 9.5 11.2
Innym zastosowaniem wywołań zwrotnych jest powiadamianie wywołujących o określonych zdarzeniach, które umożliwia pewną elastyczność czasu statycznego / kompilacji.
Osobiście korzystam z lokalnej biblioteki optymalizacji, która używa dwóch różnych wywołań zwrotnych:]}
- pierwsze wywołanie zwrotne jest wywoływane, jeśli wymagana jest wartość funkcji i gradient na podstawie wektora wartości wejściowych (logic callback: określenie wartości funkcji / wyprowadzenie gradientu).
- drugie wywołanie zwrotne jest wywoływane raz dla każdego kroku algorytmu i otrzymuje pewne informacje o zbieżności algorytmu (callback powiadomień).
Dlatego projektant bibliotek nie jest odpowiedzialny za decydowanie o tym, co dzieje się z informacjami przekazywanymi programiście poprzez callback powiadomień i nie musi się martwić, jak acutally określić wartości funkcji, ponieważ są one dostarczane przez wywołania zwrotnego logiki. Poprawienie tych rzeczy jest zadaniem ze względu na użytkownika biblioteki i utrzymuje bibliotekę szczupłą i bardziej ogólną.
Ponadto wywołania zwrotne mogą włączyć dynamiczne zachowanie środowiska wykonawczego.
Wyobraź sobie jakąś klasę silnika gry, która ma funkcję, która jest uruchamiana, za każdym razem, gdy użytkownicy naciskają przycisk na klawiaturze i zestaw funkcji, które kontrolują twoje zachowanie w grze. Za pomocą wywołań zwrotnych można (ponownie)decydować w czasie wykonywania, które działania zostaną 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
używa wywołań zwrotnych przechowywanych w actions
, aby uzyskać pożądane zachowanie po naciśnięciu określonego klawisza.
Jeśli gracz wybiera opcję zmiany przycisku do skoków, silnik może wywołać
game_core_instance.update_keybind(newly_selected_key, &player_jump);
I tym samym zmienić zachowanie wywołania na key_pressed
(które wywołania player_jump
) po naciśnięciu tego przycisku następnym razem w grze.
Czym są wywołania w C++(11)?
Aby uzyskać bardziej formalny opis, Zobacz C++ concepts: Callable on cppreference.Funkcjonalność wywołania zwrotnego może być realizowana na kilka sposobów w C++(11), ponieważ okazuje się, że kilka różnych rzeczy callable*:
- Wskaźniki funkcji (w tym wskaźniki do funkcji Członkowskich)
-
std::function
obiekty - wyrażenia Lambda
- wyrażenia Bind
- Obiekty funkcyjne (klasy z przeciążonym operatorem wywołania funkcji
operator()
)
* Uwaga: wskaźnik do członków danych również można wywołać, ale żadna funkcja nie jest wywoływana.
Kilka ważnych sposobów zapisu wywołań zwrotnych w detal
- X. 1 "zapisanie" wywołania zwrotnego w tym poście oznacza składnię do zadeklarowania i nazwania typu wywołania zwrotnego.
- X. 2 "wywołanie" wywołania zwrotnego 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, wywołanie podobne do f(...)
może być napisane jako std::invoke(f, ...)
, które również obsługuje wskaźnik do sprawy członka.
1. Funkcja wskaźniki
Wskaźnik funkcji jest 'najprostszym' (pod względem ogólności; pod względem czytelności prawdopodobnie najgorszym) typu callback może mieć.
Niech będzie prosta funkcja foo
:
int foo (int x) { return 2+x; }
1.1 zapis wskaźnika funkcji / notacji typu
A typ wskaźnika funkcji ma zapis
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
Gdzie nazwany wskaźnik funkcji będzie wyglądał tak
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;
Deklaracja using
daje nam możliwość dokonania rzeczy nieco bardziej czytelne, ponieważ typedef
dla f_int_t
można również zapisać jako:
using f_int_t = int(*)(int);
Gdzie (przynajmniej dla mnie) jest jaśniejsze, że f_int_t
jest nowym aliasem typu i rozpoznawanie typu wskaźnika funkcji jest również łatwiejsze
I deklaracja funkcji wykorzystującej wywołanie zwrotne typu wskaźnika funkcji będzie brzmiała:
// 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);
1.2 zapis wywołania zwrotnego
Notacja wywołania jest zgodna ze składnią wywołania prostej 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
}
1.3 Użycie notacji zwrotnej i kompatybilne typy
Funkcja zwrotna przyjmująca wskaźnik funkcji może być wywołana za pomocą wskaźników funkcji.
Użycie 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
1.4 przykład
Funkcja ca być napisane, że nie zależy od tego, jak callback działa:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
Gdzie możliwe wywołania mogą być
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};
2. Wskaźnik do członka function
Wskaźnik do funkcji członka (pewnej klasy C
) jest specjalnym typem (i jeszcze 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; }
};
2.1 zapis wskaźnika do funkcji member / zapisu typu
A wskaźnik do typu funkcji member dla pewnej klasy T
ma zapis
// 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 member będzie - analogicznie do wskaźnika funkcji-wyglądał następująco to:
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: deklaracja funkcji przyjmującej wskaźnik do funkcji member callback 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);
2.2 zapis wywołania zwrotnego
Funkcja wskaźnik do członka C
może być wywołana w odniesieniu do obiektu typu C
poprzez użycie operacji dostępu do członka na dereferenced pointer.
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 dostępny jest wskaźnik do C
, składnia jest równoważna (gdzie wskaźnik do C
musi być również dereferowany):
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);
}
2.3 notacja użycia wywołania zwrotnego i typy kompatybilne
Funkcja zwrotna przyjmująca wskaźnik funkcji członka klasy T
może być wywołana za pomocą wskaźnika funkcji członka klasy T
.
Używanie funkcji, która pobiera wskaźnik do funkcji member callback jest-analogicznie do wskaźników 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
3. std::function
objects (header <functional>
)
The std::function
class jest polimorficznym opakowaniem funkcji do przechowywania, kopiowania lub wywoływania wywołań.
3.1 zapis a std::function
zapis obiektu / typu
Typ obiektu a std::function
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 zapis wywołania zwrotnego
Klasa std::function
ma zdefiniowaną operator()
, która może być użyta do wywołania jej celu.
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 użycia wywołania zwrotnego i typy kompatybilne
Wywołanie zwrotne std::function
jest bardziej ogólne niż Wskaźniki funkcji lub wskaźnik do funkcji member, ponieważ różne typy mogą być przekazywane i domyślnie przekształcane w obiekt std::function
.
3.3.1 Wskaźniki funkcji i wskaźniki do funkcji Członkowskich
Wskaźnik funkcji
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
Lub wskaźnik do funkcji member
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żywany.
3.3.2 wyrażenia Lambda
W przypadku wyrażenia lambda można zapisać w obiekciestd::function
bez nazwy zamknięcie z wyrażenia lambda:
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 wyrażenia std::bind
może zostać przekazany. 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 wywołania wskaźnika do funkcji Członkowskich:
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 posiadające odpowiednie przeciążenie operator()
mogą być również przechowywane wewnątrz obiektu std::function
.
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 )
3.4 przykład
Zmiana przykład użycia wskaźnika funkcji 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ęcej użyteczności tej funkcji, ponieważ (patrz 3.3) mamy więcej możliwości jej użycia:
// 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};
4. Templated callback type
Używając szablonów, kod wywołujący callback może być jeszcze bardziej ogólny niż przy użyciu obiektów std::function
.
zauważ, że szablony są funkcją czasu kompilacji i są narzędziem do projektowania polimorfizmu w czasie kompilacji. Jeśli ma zostać osiągnięte dynamiczne zachowanie środowiska uruchomieniowego poprzez wywołania zwrotne, szablony pomogą, ale nie będą wywoływać dynamiki uruchomieniowej.
4.1 zapisywanie (zapis typu) i wywoływanie szablonowych wywołań zwrotnych]}Uogólnienie tzn. kodu std_ftransform_every_int
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]);
}
}
W przeciwieństwie do innych typów wywołania zwrotnego, Typ wywołania zwrotnego nie może być używany do wywołania zwrotnego.]}
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 wyjście wyświetla nazwę typu wydedukowaną dla template type F
. Implementacja type_name
jest podana na końcu tego posta.
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;
}
4.2 przykłady wykorzystujące szablony wywołań zwrotnych i kompatybilne typy
Typy kompatybilne dla szablonowej metody wywołania zwrotnego std::function
stdf_transform_every_int_templ
są identyczne z wyżej wymienionymi typami (patrz 3.4).
Korzystanie z wersji szablonowej jednak sygnatura użytego 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 powyżej) działa z 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";
}
Zwykły parametr szablonu transform_every_int_templ
może być każdym możliwym typem wywoływalnym.
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 użyta 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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-01 18:08:00
Istnieje również sposób wykonywania wywołań zwrotnych 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 klasy jako wywołania zwrotne, deklaracje do tych wskaźników funkcji mają bardziej złożone deklaracje, 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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-24 22:24:45
Scott Meyers podaje dobry 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<>
jest "nowoczesnym" sposobem pisania wywołań zwrotnych w C++.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-26 12:41:44
A funkcja zwrotna jest metodą, która jest przekazywana do procedury i wywoływana w pewnym momencie przez rutynę, 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) w dużym stopniu korzysta z wywołań zwrotnych.
Na przykład, jeśli chcesz pracować z plikami w folderze - możesz wywołać funkcję API, z własną procedurą, a twoja procedura zostanie uruchomiona raz na plik w określonym folderze. Pozwala to na interfejs API jest bardzo elastyczny.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-19 17:20:07
Przyjęta odpowiedź jest bardzo przydatna i dość wyczerpująca. Jednak po stwierdza
Chciałbym zobaczyć prosty przykład do napisania funkcji zwrotnej.
Więc proszę bardzo, 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 w jakiś sposób realny, ponieważ chcesz wywołać funkcję print_hashes
z różnymi implementacjami funkcji hashowych, w tym celu podałem prostą. Otrzymuje on łańcuch znaków, zwraca int( wartość hash podanego ciągu znaków), a wszystko, co musisz zapamiętać z części składni, to std::function<int (const std::string&)>
, która opisuje taką funkcję jako argument wejściowy funkcji, która ją wywoła.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-16 09:08:16
Nie ma jednoznacznej koncepcji funkcji zwrotnej w C++. Mechanizmy wywołania zwrotnego są często implementowane za pomocą wskaźników funkcji, obiektów funkcyjnych lub obiektów wywołania zwrotnego. Programiści muszą wyraźnie zaprojektować i wdrożyć funkcję callback.
Edit based on feedback:
Pomimo negatywnego sprzężenia zwrotnego ta odpowiedź została otrzymana, nie jest zła. Postaram się lepiej wyjaśnić, skąd pochodzę.
C i C++ mają wszystko potrzeba wdrożenia funkcji callback. Najczęstszym i trywialnym sposobem implementacji funkcji zwrotnej jest przekazanie wskaźnika funkcji jako argumentu funkcji.
Jednak funkcje zwrotne i Wskaźniki funkcji nie są synonimami. Wskaźnik funkcji jest mechanizmem językowym, podczas gdy funkcja zwrotna jest pojęciem semantycznym. Wskaźniki funkcyjne nie są jedynym sposobem implementacji funkcji callback - można również korzystać z funkcji funkcyjnych, a nawet wirtualnych funkcji garden variety. Co sprawia, że wywołanie funkcji wywołanie zwrotne nie jest mechanizmem używanym do identyfikacji i wywołania funkcji, ale kontekstem i semantyką wywołania. Mówiąc coś jest funkcją zwrotną oznacza większy niż normalny rozdział między funkcją wywołującą a określoną funkcją wywoływaną, luźniejsze sprzężenie koncepcyjne między wywołującym i wywołującym, z wywołującym ma wyraźną kontrolę nad tym, co zostanie wywołane. Jest to, że rozmyte pojęcie luźniejszego sprzężenia pojęciowego i wyboru funkcji wywołującej to sprawia, że coś funkcja zwrotna, a nie korzystanie ze wskaźnika funkcji.
Na przykład dokumentacja. NET dla IFormatProvider mówi, że "GetFormat jest metodą wywołania zwrotnego", nawet jeśli jest to tylko metoda interfejsu typu run-of-the-mill. Nie sądzę, żeby ktokolwiek argumentował, że wszystkie wirtualne wywołania metody są funkcjami callback. To, co sprawia, że GetFormat metoda callback nie jest mechanika jak to jest przekazywane lub wywoływane, ale semantyka wybierania dzwoniącego, który zostanie wywołana metoda GetFormat obiektu.
Niektóre języki zawierają funkcje z jawną semantyką wywołania zwrotnego, zazwyczaj związane ze zdarzeniami i obsługą zdarzeń. Na przykład, C# ma typ event ze składnią i semantyką jawnie zaprojektowaną wokół koncepcji wywołań zwrotnych. Visual Basic posiada klauzulę Handles , która jawnie deklaruje metodę jako funkcję wywołania zwrotnego, jednocześnie wyodrębniając pojęcie delegatów lub wskaźników funkcji. W takich przypadkach semantyczna koncepcja wywołania zwrotnego jest zintegrowana z samym językiem.
C i c++, z drugiej strony, nie osadzają semantycznej koncepcji funkcji zwrotnych prawie tak wyraźnie. Mechanizmy istnieją, semantyka zintegrowana nie. Możesz zaimplementować funkcje zwrotne, ale aby uzyskać coś bardziej wyrafinowanego, co obejmuje jawną semantykę zwrotną, musisz zbudować ją na podstawie tego, co zapewnia C++, takich jak to, co Qt zrobiło z ich sygnałami i Sloty .
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 raise, emitować, uchwyty, event + = , itd. Jeśli pochodzisz z języka z tego typu elementami, natywne wsparcie dla połączeń zwrotnych w C++ będzie czuć się wykastrowany.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-28 16:20:22
Funkcje zwrotne są częścią standardu C, a więc również częścią C++. Ale jeśli pracujesz z C++, sugerowałbym użycie observer pattern zamiast: http://en.wikipedia.org/wiki/Observer_pattern
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 16:45:02
Zobacz powyższą definicję, gdzie stwierdza, ż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. Kiedy to zrobisz, masz dostęp do danych członkowskich. Jeśli używasz C sposób definiowania wywołania zwrotnego trzeba będzie skierować go do statycznej funkcji członka. Nie jest to zbyt pożądane.
Oto jak możesz używać wywołań zwrotnych w C++. Załóżmy 4 pliki. Parę .CPP/H pliki dla każdej klasy. Klasa C1 jest klasą z metodą, którą chcemy wywołać. C2 odwołuje się do metody C1. W tym przykładzie funkcja callback pobiera 1 parametr, który dodałem dla czytelników. Przykład nie pokazuje żadnych obiektów, które są tworzone i używane. Jednym z przypadków użycia tej implementacji jest posiadanie jednej klasy, która odczytuje i przechowuje dane w przestrzeni tymczasowej, a drugiej, która przetwarza dane post. Z funkcją callback, dla każdego wiersza danych odczytu callback może następnie przetworzyć. Ta technika wycina napowietrzną wymaganą przestrzeń tymczasową. 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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-23 13:09:26
Boost ' S singals2 pozwala na subskrybowanie ogólnych funkcji Członkowskich (bez szablonów!) i threadsafe way.
Przykład: sygnały widoku dokumentu mogą być używane do implementacji elastycznych Architektury widoku dokumentu. Dokument będzie zawierał sygnał do które każdy z widoków może połączyć. Następująca Klasa dokumentu definiuje prosty dokument tekstowy obsługujący widoki wielokrotne. Zauważ, że przechowuje jeden sygnał, na który wszystkie widoki będą połączone.
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;
};
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-15 19:50:41