Funkcje zwrotne w c++

W c++, kiedy i jak używać funkcji callback?

EDIT:
Chciałbym zobaczyć prosty przykład do napisania funkcji zwrotnej.

Author: Ross Rogers, 2010-02-19

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 obiekcie std::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.

Biblioteka ta jest częścią biblioteki standardowej, a mianowicie biblioteki standardowej.]}, który jest również wzorowany w odniesieniu do typów iteracyjnych.
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;
}
 301
Author: Pixelchemist,
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);
}
 146
Author: Ramon Zarazua B.,
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++.

 63
Author: Karl von Moor,
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.

 37
Author: Reed Copsey,
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.

 9
Author: Miljen Mikic,
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.

 7
Author: Darryl,
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

 6
Author: AudioDroid,
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);
}
 4
Author: Gravy Jones,
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;
};
 0
Author: crizCraig,
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