436 Stimmen

Rückruf-Funktionen in C++

Wann und wie verwendet man in C++ eine Callback-Funktion?

EDIT :
Ich würde gerne ein einfaches Beispiel für eine Callback-Funktion sehen.

680voto

Pixelchemist Punkte 22777

Anmerkung: Die meisten Antworten beziehen sich auf Funktionszeiger, was eine Möglichkeit ist, "Callback"-Logik in C++ zu erreichen, aber bis heute nicht die günstigste, wie ich finde.

Was sind Callbacks(?) und warum sollte man sie verwenden(!)

Ein Rückruf ist eine aufrufbar (siehe weiter unten), die von einer Klasse oder Funktion akzeptiert werden, um die aktuelle Logik in Abhängigkeit von diesem Rückruf anzupassen.

Ein Grund für die Verwendung von Callbacks ist das Schreiben von generisch Code, der unabhängig von der Logik in der aufgerufenen Funktion ist und mit verschiedenen Rückrufen wiederverwendet werden kann.

Viele Funktionen der Standard-Algorithmen-Bibliothek <algorithm> Rückrufe verwenden. Zum Beispiel die for_each Algorithmus wendet einen unären Rückruf auf jedes Element in einem Bereich von Iteratoren an:

template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
  for (; first != last; ++first) {
    f(*first);
  }
  return f;
}

die verwendet werden kann, um einen Vektor zunächst zu inkrementieren und dann zu drucken, indem beispielsweise entsprechende Callables übergeben werden:

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 << " "; });

die druckt

5 6.2 8 9.5 11.2

Eine weitere Anwendung von Callbacks ist die Benachrichtigung von Aufrufern über bestimmte Ereignisse, was ein gewisses Maß an statischer/kompilierzeitlicher Flexibilität ermöglicht.

Ich persönlich verwende eine lokale Optimierungsbibliothek, die zwei verschiedene Rückrufe verwendet:

  • Der erste Callback wird aufgerufen, wenn ein Funktionswert und der Gradient basierend auf einem Vektor von Eingabewerten benötigt wird (Logik-Callback: Funktionswertbestimmung / Gradientenableitung).
  • Der zweite Callback wird einmal für jeden Algorithmus-Schritt aufgerufen und erhält bestimmte Informationen über die Konvergenz des Algorithmus (Notification Callback).

Der Bibliotheksdesigner ist also nicht dafür zuständig, zu entscheiden, was mit den Informationen geschieht, die dem Programmierer gegeben werden über den Benachrichtigungs-Callback gegeben werden, und er muss sich nicht darum kümmern, wie die Funktionswerte tatsächlich bestimmt werden, da sie vom Logik-Callback bereitgestellt werden. Diese Dinge richtig zu machen, ist eine Aufgabe, die dem Bibliotheksbenutzer obliegt, und hält die Bibliothek schlank und generischer.

Außerdem können Rückrufe ein dynamisches Laufzeitverhalten ermöglichen.

Stellen Sie sich eine Art Spiel-Engine-Klasse vor, die eine Funktion hat, die jedes Mal ausgelöst wird, wenn der Benutzer eine Taste auf seiner Tastatur drückt, und eine Reihe von Funktionen, die das Verhalten des Spiels steuern. Mit Callbacks können Sie zur Laufzeit (neu) entscheiden, welche Aktion ausgeführt werden soll.

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;
    }
};

Hier wird die Funktion key_pressed verwendet die Rückrufe, die in actions um das gewünschte Verhalten zu erreichen, wenn eine bestimmte Taste gedrückt wird. Wenn der Spieler die Taste für das Springen ändern möchte, kann die Engine Folgendes aufrufen

game_core_instance.update_keybind(newly_selected_key, &player_jump);

und damit das Verhalten eines Aufrufs an key_pressed (die die Anrufe player_jump ), sobald diese Taste das nächste Mal im Spiel gedrückt wird.

Was sind Abrufbar unter in C++(11)?

Véase C++-Konzepte: Aufrufbar auf cppreference für eine formalere Beschreibung.

Callback-Funktionalität kann in C++(11) auf verschiedene Weise realisiert werden, da sich mehrere verschiedene Dinge als abrufbar* :

  • Funktionszeiger (einschließlich Zeigern auf Mitgliedsfunktionen)
  • std::function Objekte
  • Lambda-Ausdrücke
  • Ausdrücke binden
  • Funktionsobjekte (Klassen mit überladenem Funktionsaufrufoperator operator() )

* <em>Hinweis: Zeiger auf Datenelemente sind ebenfalls aufrufbar, aber es wird überhaupt keine Funktion aufgerufen.</em>

Mehrere wichtige Arten zu schreiben Rückrufe im Einzelnen

  • X.1 "Schreiben" eines Rückrufs bedeutet in diesem Beitrag die Syntax zur Deklaration und Benennung des Rückruftyps.
  • X.2 Das "Aufrufen" eines Rückrufs bezieht sich auf die Syntax zum Aufrufen dieser Objekte.
  • X.3 "Verwendung" eines Rückrufs bezeichnet die Syntax bei der Übergabe von Argumenten an eine Funktion unter Verwendung eines Rückrufs.

Hinweis: Ab C++17 kann ein Aufruf wie f(...) kann geschrieben werden als std::invoke(f, ...) die auch den Zeiger auf den Mitgliedsfall behandelt.

1. Funktionszeiger

Ein Funktionszeiger ist der "einfachste" (im Sinne der Allgemeinheit; im Sinne der Lesbarkeit wohl der schlechteste) Typ, den ein Rückruf haben kann.

Nehmen wir eine einfache Funktion foo :

int foo (int x) { return 2+x; }

1.1 Schreiben eines Funktionszeigers / Typennotation

A Funktionszeigertyp hat die Notation

return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)

wobei a benannter Funktionszeiger wird wie folgt aussehen

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;

En using Deklaration gibt uns die Möglichkeit, die Dinge ein wenig lesbarer zu machen, da die typedef para f_int_t kann auch geschrieben werden als:

using f_int_t = int(*)(int);

Wo es (zumindest für mich) klarer ist, dass f_int_t ist der neue Typ-Alias und die Erkennung des Funktionszeigertyps ist ebenfalls einfacher

Und eine Erklärung eines Funktion unter Verwendung eines Rückrufs vom Typ Funktionszeiger sein wird:

// 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 Notation für Rückrufe

Die Aufrufnotation folgt der einfachen Funktionsaufrufsyntax:

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 Notation für Rückrufe und kompatible Typen

Eine Callback-Funktion, die einen Funktionszeiger annimmt, kann mit Funktionszeigern aufgerufen werden.

Die Verwendung einer Funktion, die einen Funktionszeiger als Rückruf akzeptiert, ist recht einfach:

 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 Beispiel

Es kann eine Funktion geschrieben werden, die nicht davon abhängt, wie der Rückruf funktioniert:

void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = fp(v[i]);
  }
}

wo mögliche Rückrufe sein könnten

int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }

verwendet wie

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. Zeiger auf Mitgliedsfunktion

Ein Zeiger auf eine Mitgliedsfunktion (einer Klasse C ) ist ein spezieller (und noch komplexerer) Funktionszeiger, der ein Objekt vom Typ C zu bedienen.

struct C
{
    int y;
    int foo(int x) const { return x+y; }
};

2.1 Schreiben eines Zeigers auf eine Mitgliedsfunktion / Typnotation

A Zeiger auf den Typ der Mitgliedsfunktion für einige Klasse T hat die Notation

// 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)

wobei a benannter Zeiger auf eine Mitgliedsfunktion wird - in Analogie zum Funktionszeiger - wie folgt aussehen:

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;

Beispiel: Deklaration einer Funktion, die eine Zeiger auf den Rückruf der Mitgliedsfunktion als eines seiner Argumente:

// 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 Notation von Rückrufaufrufen

Der Zeiger auf die Memberfunktion von C kann in Bezug auf ein Objekt des Typs C mit Hilfe von Zugriffsoperationen auf den dereferenzierten Zeiger. Hinweis: Klammerzusatz erforderlich!

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
}

Hinweis: Wenn ein Zeiger auf C verfügbar ist, ist die Syntax gleichwertig (wobei der Zeiger auf C muss ebenfalls dereferenziert werden):

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 Notation für Rückrufe und kompatible Typen

Eine Callback-Funktion, die einen Zeiger auf eine Mitgliedsfunktion der Klasse T kann mit einem Zeiger auf eine Mitgliedsfunktion der Klasse aufgerufen werden T .

Die Verwendung einer Funktion, die einen Zeiger auf den Rückruf einer Mitgliedsfunktion enthält, ist - in Analogie zu Funktionszeigern - ebenfalls recht einfach:

 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 Objekte (Kopf <functional> )

En std::function Klasse ist eine polymorphe Funktionsumhüllung zum Speichern, Kopieren oder Aufrufen von Callables.

3.1 Schreiben einer std::function Objekt-/Typ-Notation

Der Typ eines std::function Objekt, das eine Callable speichert, sieht so aus:

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 Notation von Rückrufaufrufen

Die Klasse std::function hat operator() definiert, die zum Aufrufen des Ziels verwendet werden kann.

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 Notation für Rückrufe und kompatible Typen

En std::function Callback ist generischer als Funktionszeiger oder Zeiger auf Mitgliedsfunktionen, da verschiedene Typen übergeben und implizit in eine std::function Objekt.

3.3.1 Funktionszeiger und Zeiger auf Mitgliedsfunktionen

Ein Funktionszeiger

int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )

oder ein Zeiger auf eine Mitgliedsfunktion

int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )

verwendet werden können.

3.3.2 Lambda-Ausdrücke

Eine unbenannte Schließung aus einem Lambda-Ausdruck kann in einer std::function Objekt:

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 Ausdrücke

Das Ergebnis einer std::bind Ausdruck übergeben werden kann. Zum Beispiel durch die Bindung von Parametern an einen Funktionszeigeraufruf:

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 )

Wobei auch Objekte als Objekt für den Aufruf von Zeigern auf Mitgliedsfunktionen gebunden werden können:

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 Funktionsobjekte

Objekte von Klassen, die eine eigene operator() Überlast kann innerhalb einer std::function Objekt auch.

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 Beispiel

Ändern des Funktionszeiger-Beispiels zur Verwendung von 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]);
  }
}

gibt dieser Funktion einen viel größeren Nutzen, weil wir (siehe 3.3) mehr Möglichkeiten haben, sie zu verwenden:

// 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. Vorgefertigter Rückruftyp

Bei der Verwendung von Vorlagen kann der Code, der den Rückruf aufruft, noch allgemeiner sein als bei der Verwendung von std::function Objekte.

Beachten Sie, dass Schablonen eine Funktion zur Kompilierzeit sind und ein Entwurfswerkzeug für die Polymorphie zur Kompilierzeit darstellen. Wenn dynamisches Verhalten zur Laufzeit durch Rückrufe erreicht werden soll, sind Vorlagen hilfreich, aber sie bewirken keine Dynamik zur Laufzeit.

4.1 Schreiben (Typnotationen) und Aufrufen von Rückrufvorlagen

Die Verallgemeinerung, d.h. die std_ftransform_every_int Code von oben kann durch die Verwendung von Vorlagen noch weiter verbessert werden:

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]);
  }
}

mit einer noch allgemeineren (und einfacheren) Syntax für einen Rückruftyp, der ein einfaches, schablonenhaftes Argument ist:

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]);
  }
}

_Hinweis: Die mitgelieferte Ausgabe gibt den für den Templatetyp abgeleiteten Typnamen aus F . Die Umsetzung der type_name finden Sie am Ende dieses Beitrags._

Die allgemeinste Implementierung für die unäre Transformation eines Bereichs ist Teil der Standardbibliothek, nämlich std::transform , die auch in Bezug auf die iterierten Typen schablonenhaft ist.

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 Beispiele für die Verwendung von Rückrufvorlagen und kompatiblen Typen

Die kompatiblen Typen für die Schablone std::function Callback-Methode stdf_transform_every_int_templ sind identisch mit den oben genannten Typen (siehe 3.4).

Bei der Verwendung der Vorlagenversion kann sich jedoch die Signatur des verwendeten Rückrufs ein wenig ändern:

// 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);

_Note : std_ftransform_every_int (nicht-schablonierte Version; siehe oben) funktioniert auch mit foo aber nicht mit 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";
}

Der einfache Schablonenparameter von transform_every_int_templ kann jeder mögliche abrufbare Typ sein.

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);

Der obige Code wird gedruckt:

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 oben verwendete Implementierung

#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;
}

180voto

Ramon Zarazua B. Punkte 6825

Es gibt auch die C-Methode für Rückrufe: Funktionszeiger

// 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);
}

Wenn Sie nun Klassenmethoden als Rückrufe übergeben wollen, haben die Deklarationen zu diesen Funktionszeigern zum Beispiel komplexere Deklarationen:

// 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);
}

79voto

Karl von Moor Punkte 8284

Scott Meyers gibt ein schönes Beispiel:

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;
};

Ich denke, das Beispiel sagt alles.

std::function<> ist die "moderne" Art, C++-Rückrufe zu schreiben.

38voto

Reed Copsey Punkte 536986

A Rückruf-Funktion ist eine Methode, die an eine Routine übergeben wird und irgendwann von der Routine, an die sie übergeben wird, aufgerufen wird.

Dies ist sehr nützlich für die Erstellung wiederverwendbarer Software. Viele Betriebssystem-APIs (z. B. die Windows-API) verwenden beispielsweise häufig Rückrufe.

Wenn Sie zum Beispiel mit Dateien in einem Ordner arbeiten möchten, können Sie eine API-Funktion mit Ihrer eigenen Routine aufrufen, und Ihre Routine wird einmal pro Datei im angegebenen Ordner ausgeführt. Dadurch ist die API sehr flexibel.

34voto

Miljen Mikic Punkte 14059

Die akzeptierte Antwort ist sehr nützlich und recht umfassend. Allerdings erklärt der OP

Ich würde gerne eine einfaches Beispiel um eine Rückruf-Funktion zu schreiben.

So, jetzt geht's los, ab C++11 haben Sie std::function Es besteht also keine Notwendigkeit für Funktionszeiger und ähnliche Dinge:

#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;
}

Dieses Beispiel ist übrigens irgendwie real, denn Sie wollen die Funktion print_hashes mit verschiedenen Implementierungen von Hash-Funktionen, für diesen Zweck habe ich eine einfache bereitgestellt. Sie empfängt eine Zeichenkette, gibt einen int zurück (einen Hash-Wert der angegebenen Zeichenkette), und alles, was Sie sich vom Syntaxteil merken müssen, ist std::function<int (const std::string&)> die eine solche Funktion als Eingangsargument der Funktion beschreibt, die sie aufruft.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X