Terugbelfuncties in C++

In C++, wanneer en hoe gebruik je een callback-functie?

BEWERKEN:
Ik zou graag een eenvoudig voorbeeld zien om een callback-functie te schrijven.


Antwoord 1, autoriteit 100%

Opmerking: de meeste antwoorden hebben betrekking op functieaanwijzers, wat een mogelijkheid is om “callback” -logica in C++ te bereiken, maar vanaf vandaag niet de meest gunstige denk ik.

Wat zijn callbacks(?) en waarom gebruik je ze(!)

Een callback is een callable(zie verderop) die wordt geaccepteerd door een klasse of functie en wordt gebruikt om de huidige logica aan te passen, afhankelijk van die callback.

Een reden om callbacks te gebruiken is om algemenecode te schrijven die onafhankelijk is van de logica in de aangeroepen functie en kan worden hergebruikt met verschillende callbacks.

Veel functies van de bibliotheek met standaardalgoritmen <algorithm>gebruiken callbacks. Het for_each-algoritme past bijvoorbeeld een unaire callback toe op elk item in een reeks iterators:

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

die kan worden gebruikt om eerst een vector te verhogen en vervolgens af te drukken door de juiste callables door te geven, bijvoorbeeld:

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 wordt afgedrukt

5 6.2 8 9.5 11.2

Een andere toepassing van callbacks is de melding van bellers van bepaalde gebeurtenissen die een bepaalde hoeveelheid statische / compileertijdflexibiliteit mogelijk maakt.

Persoonlijk gebruik ik een lokale optimalisatie-bibliotheek die twee verschillende callbacks gebruikt:

  • De eerste callback wordt genoemd als een functiewaarde en de gradiënt op basis van een vector van ingangswaarden vereist is (logic callback: functiewaardewaarde / verloop derivering).
  • De tweede callback wordt eenmaal gebeld voor elke algoritme-stap en ontvangt bepaalde informatie over de convergentie van het algoritme (meldingsopballer).

Dus de bibliotheekontwerper is niet verantwoordelijk voor het beslissen wat er gebeurt met de informatie die wordt gegeven aan de programmeur
via de melding terugbellen en hoeven hij zich geen zorgen te maken over het bepalen van de functie waarden omdat ze worden geleverd door de logica-terugbellen. Die dingen goed krijgen is een taak als gevolg van de bibliotheekgebruiker en houdt de bibliotheek slank en meer generiek.

Bovendien kunnen callbacks dynamisch runtime-gedrag mogelijk maken.

Stel je een soort van game-motorklasse voor die een functie heeft die is ontslagen, telkens wanneer de gebruikers op een knop op zijn toetsenbord en een reeks functies drukken die je spelgedrag regelen.
Met callbacks kunt u (her) beslissen bij runtime welke actie zal worden ondernomen.

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 gebruikt de functie key_pressedde callbacks die zijn opgeslagen in actionsom het gewenste gedrag te verkrijgen wanneer een bepaalde toets wordt ingedrukt.
Als de speler ervoor kiest om de knop voor het springen te veranderen, kan de engine

game_core_instance.update_keybind(newly_selected_key, &player_jump);

en dus het gedrag van een oproep veranderen in key_pressed(die de oproep player_jump) zodra deze knop de volgende keer in het spel wordt ingedrukt.

Wat zijn callablesin C++(11)?

Zie C++ concepten: Callableop cppreference voor een meer formele beschrijving.

Callback-functionaliteit kan op verschillende manieren worden gerealiseerd in C++(11) aangezien verschillende dingen callable*blijken te zijn:

  • Functiewijzers (inclusief verwijzingen naar ledenfuncties)
  • std::functionobjecten
  • Lambda-uitdrukkingen
  • Uitdrukkingen binden
  • Functieobjecten (klassen met overbelaste functieaanroep operator operator())

* Opmerking: aanwijzer naar gegevensleden kan ook worden aangeroepen, maar er wordt helemaal geen functie aangeroepen.

Verschillende belangrijke manieren om callbacksin detail te schrijven

  • X.1 “Het schrijven” van een callback in dit bericht betekent de syntaxis om het callback-type te declareren en een naam te geven.
  • X.2 Een callback “aanroepen” verwijst naar de syntaxis om die objecten aan te roepen.
  • X.3 “Gebruiken” van een callback betekent de syntaxis bij het doorgeven van argumenten aan een functie met behulp van een callback.

Opmerking: vanaf C++17 kan een aanroep als f(...)worden geschreven als std::invoke(f, ...)die ook de verwijzing naar de ledencase afhandelt.

1. Functiewijzers

Een functiepointer de ‘eenvoudigste’ (in termen van algemeenheid, wat betreft leesbaarheid ongetwijfeld het slechtste). Typ callback hebben

Laten we een eenvoudige functie foo

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

1.1 Het schrijven van een functie pointer / soort notatie

A typefunctie pointer heeft de notatie

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

waarbij een benoemde functie pointer type zal uitzien

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;

usingverklaring geeft ons de mogelijkheid om dingen een beetje beter leesbaar te maken, omdat de typedefvoor f_int_tkan ook worden geschreven als :

using f_int_t = int(*)(int);

Waar (althans voor mij) is het duidelijker dat f_int_tis het nieuwe type alias en erkenning van de functie pointer type wordt ook makkelijker

En een verklaring van een functie met behulp van een callback van het type functie pointer zal zijn:

// 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 Notatie terugbeloproep

De aanroepnotatie volgt de eenvoudige syntaxis van de functieaanroep:

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 Gebruik notatie voor terugbellen en compatibele typen

Een callback-functie die een functieaanwijzer gebruikt, kan worden aangeroepen met behulp van functieaanwijzers.

Het gebruik van een functie waarvoor een functieaanwijzer nodig is, is vrij eenvoudig:

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 Voorbeeld

Er kan een functie worden geschreven die niet afhankelijk is van hoe de callback werkt:

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

waar mogelijk terugbellen

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

gebruikt als

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. Aanwijzer naar lidfunctie

Een aanwijzer naar lidfunctie (van een bepaalde klasse C) is een speciaal type (en zelfs complexere) functieaanwijzer waarvoor een object van het type Cnodig is om te werken aan.

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

2.1 Aanwijzer naar lidfunctie / typenotatie schrijven

Een pointer naar lidfunctietypevoor een bepaalde klasse Theeft de notatie

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

waar een benoemde aanwijzer naar lidfunctieer -naar analogie van de functieaanwijzer- als volgt uit zal zien:

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;

Voorbeeld: een functie declareren die een pointer naar lidfunctie callbackals een van de argumenten gebruikt:

// 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 Notatie terugbeloproep

De pointer naar lidfunctie van Ckan worden aangeroepen met betrekking tot een object van het type Cdoor gebruik te maken van lidtoegangsbewerkingen op de gederefereerde aanwijzer.
Opmerking: haakjes vereist!

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
}

Opmerking: als een verwijzing naar Cbeschikbaar is, is de syntaxis equivalent (waarbij de verwijzing naar Cook moet worden verwijderd):

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 Gebruik notatie voor terugbellen en compatibele typen

Een callback-functie die een lidfunctieaanwijzer van klasse Tgebruikt, kan worden aangeroepen met behulp van een lidfunctieaanwijzer van klasse T.

Het gebruik van een functie die een verwijzing naar een functieaanroep van een lid nodig heeft, is -naar analogie van functieaanwijzers- ook vrij eenvoudig:

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::functionobjecten (header <functional>)

De klasse std::functionis een polymorfe functie-wrapper om aanroepbare aanroepen op te slaan, te kopiëren of aan te roepen.

3.1 Een std::functionobject / type notatie schrijven

Het type van een std::function-object dat een oproepbare opslaat, ziet er als volgt uit:

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 Terugbelnotatie

De klasse std::functionheeft operator()gedefinieerd die kan worden gebruikt om zijn doel aan te roepen.

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 Gebruik notatie voor terugbellen en compatibele typen

De std::functioncallback is meer generiek dan functie pointers of pointer to member functie aangezien verschillende typen kunnen worden doorgegeven en impliciet geconverteerd naar een std::functionobject .

3.3.1 Functieaanwijzers en verwijzingen naar lidfuncties

Een functiewijzer

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

of een verwijzing naar de ledenfunctie

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

kan worden gebruikt.

3.3.2 Lambda-expressies

Een naamloze afsluiting van een lambda-expressie kan worden opgeslagen in een std::functionobject:

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::bindexpressies

Het resultaat van een std::bindexpressie kan worden doorgegeven. Bijvoorbeeld door parameters te binden aan een functieaanwijzer:

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 )

Waar ook objecten kunnen worden gebonden als het object voor het aanroepen van aanwijzer naar lidfuncties:

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 Functie-objecten

Objecten van klassen met een juiste operator()overbelasting kunnen ook worden opgeslagen in een std::functionobject.

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 Voorbeeld

Het voorbeeld van de functieaanwijzer wijzigen om std::function

te gebruiken

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

geeft veel meer bruikbaarheid aan die functie omdat (zie 3.3) we meer mogelijkheden hebben om het te gebruiken:

// 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. Terugbeltype sjablonen

Met behulp van sjablonen kan de code die de callback aanroept zelfs algemener zijn dan het gebruik van std::function-objecten.

Merk op dat sjablonen een functie zijn tijdens het compileren en een ontwerptool zijn voor polymorfisme tijdens het compileren. Als runtime-dynamisch gedrag moet worden bereikt door middel van callbacks, zullen sjablonen helpen, maar ze zullen geen runtime-dynamiek veroorzaken.

4.1 Schrijven (typenotaties) en callbacks in sjablonen oproepen

Algemeen, d.w.z. de std_ftransform_every_intcode van bovenaf kan nog verder worden bereikt door sjablonen te gebruiken:

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

met een nog algemenere (en ook gemakkelijkste) syntaxis voor een callback-type als een eenvoudig, af te leiden sjabloonargument:

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

Opmerking: de meegeleverde uitvoer drukt de typenaam af die is afgeleid voor het sjabloontype F. De implementatie van type_namestaat aan het einde van dit bericht.

De meest algemene implementatie voor de unaire transformatie van een bereik maakt deel uit van de standaardbibliotheek, namelijk std::transform,
die ook een sjabloon heeft met betrekking tot de herhaalde typen.

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 Voorbeelden met gesorteerde callbacks en compatibele typen

De compatibele typen voor de gemanoneerde std::functionTerugbellen stdf_transform_every_int_templzijn identiek aan de bovengenoemde typen (zie 3.4).

De gemanoneerde versie gebruiken, kan de handtekening van de gebruikte callback echter een beetje veranderen:

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

Opmerking: std_ftransform_every_int(niet-sjablonen, zie hierboven) werkt samen met foomaar niet met 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";
}

De gewone gemanuleerde parameter van transform_every_int_templkan elk mogelijk callable type zijn.

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

De bovenstaande code wordt afgedrukt:

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_nameimplementatie hierboven gebruikt

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

Antwoord 2, autoriteit 30%

Er is ook de C-manier om callbacks uit te voeren: functiewijzers

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

Als u nu klassemethoden als callbacks wilt doorgeven, hebben de declaraties naar die functiewijzers complexere declaraties, bijvoorbeeld:

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

Antwoord 3, autoriteit 13%

Scott Meyers geeft een mooi voorbeeld:

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

Ik denk dat het voorbeeld alles zegt.

std::function<>is de “moderne” manier om C++ callbacks te schrijven.


Antwoord 4, autoriteit 7%

Een Callback-functieis een methode die wordt doorgegeven aan een routine en wordt aangeroepen op een bepaald moment door de routine waaraan het wordt doorgegeven.

Dit is erg handig voor het maken van herbruikbare software. Veel API’s van besturingssystemen (zoals de Windows API) gebruiken bijvoorbeeld veel callbacks.

Als u bijvoorbeeld met bestanden in een map wilt werken, kunt u een API-functie aanroepen, met uw eigen routine, en uw routine wordt eenmaal per bestand in de opgegeven map uitgevoerd. Hierdoor kan de API zeer flexibel zijn.


Antwoord 5, autoriteit 4%

Het geaccepteerde antwoord is erg nuttig en vrij uitgebreid. In het OP staat echter

Ik zou graag een eenvoudig voorbeeldwillen zien om een callback-functie te schrijven.

Dus hier ga je, vanaf C++11 heb je std::functiondus er zijn geen functieaanwijzers en soortgelijke dingen nodig:

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

Dit voorbeeld is trouwens op de een of andere manier echt, omdat je functie print_hasheswilt aanroepen met verschillende implementaties van hash-functies, voor dit doel heb ik een eenvoudige gegeven. Het ontvangt een tekenreeks, retourneert een int (een hash-waarde van de opgegeven tekenreeks), en alles wat u hoeft te onthouden van het syntaxisgedeelte is std::function<int (const std::string&)>die een dergelijke functie beschrijft als een invoerargument van de functie die deze zal aanroepen.


Antwoord 6, autoriteit 2%

Er is geen expliciet concept van een callback-functie in C++. Callback-mechanismen worden vaak geïmplementeerd via functieaanwijzers, functor-objecten of callback-objecten. De programmeurs moeten de callback-functionaliteit expliciet ontwerpen en implementeren.

Bewerken op basis van feedback:

Ondanks de negatieve feedback die dit antwoord heeft gekregen, is het niet verkeerd. Ik zal proberen beter uit te leggen waar ik vandaan kom.

C en C++ hebben alles wat je nodig hebt om callback-functies te implementeren. De meest gebruikelijke en triviale manier om een callback-functie te implementeren, is door een functieaanwijzer als functieargument door te geven.

Terugbelfuncties en functieaanwijzers zijn echter niet synoniem. Een functieaanwijzer is een taalmechanisme, terwijl een callback-functie een semantisch concept is. Functieaanwijzers zijn niet de enige manier om een callback-functie te implementeren – u kunt ook functors en zelfs virtuele tuinfuncties gebruiken. Wat een functieaanroep een terugroepactie maakt, is niet het mechanisme dat wordt gebruikt om de functie te identificeren en aan te roepen, maar de context en semantiek van de aanroep. Iets zeggen als een callback-functie impliceert een grotere dan normale scheiding tussen de aanroepende functie en de specifieke functie die wordt aangeroepen, een lossere conceptuele koppeling tussen de beller en de callee, waarbij de beller expliciete controle heeft over wat er wordt aangeroepen. Het is die vage notie van lossere conceptuele koppeling en oproepgestuurde functieselectie die van iets een callback-functie maakt, niet het gebruik van een functieaanwijzer.

Bijvoorbeeld, de .NET-documentatie voor IFormatProviderzegt dat “GetFormat is een callback-methode”, ook al is het maar een alledaagse interfacemethode. Ik denk niet dat iemand zou beweren dat alle virtuele methodeaanroepen callback-functies zijn. Wat GetFormat tot een callback-methode maakt, is niet de mechanica van hoe het wordt doorgegeven of aangeroepen, maar de semantiek van de beller die de GetFormat-methode van het object kiest.

Sommige talen bevatten functies met expliciete callback-semantiek, meestal gerelateerd aan gebeurtenissen en gebeurtenisafhandeling. C# heeft bijvoorbeeld het type eventmet syntaxis en semantiek die expliciet zijn ontworpen rond het concept van callbacks. Visual Basic heeft zijn Handles-clausule, die expliciet verklaart dat een methode een callback-functie is, terwijl het concept van gedelegeerden of functieaanwijzers wordt weggenomen. In deze gevallen is het semantische concept van een callback geïntegreerd in de taal zelf.

C en C++, aan de andere kant, sluiten het semantische conceptvan callback-functies niet zo expliciet in. De mechanismen zijn er, de geïntegreerde semantiek niet. Je kunt callback-functies prima implementeren, maar om iets geavanceerders te krijgen met expliciete callback-semantiek, moet je het bouwen bovenop wat C++ biedt, zoals wat Qt deed met hun Signalen en slots.

In een notendop, C++ heeft wat je nodig hebt om callbacks te implementeren, vaak vrij eenvoudig en triviaal met behulp van functiewijzers. Wat het niet heeft, zijn zoekwoorden en functies waarvan de semantiek specifiek is voor callbacks, zoals raise, emit, Handles, event + =, enz. Als je uit een taal komt met dat soort elementen, zal de native callback-ondersteuning in C++ gecastreerd aanvoelen.


Antwoord 7

Callback-functies maken deel uit van de C-standaard en dus ook van C++. Maar als je met C++ werkt, raad ik je aan om in plaats daarvan het observer-patroonte gebruiken: http://en.wikipedia.org/wiki/Observer_pattern


Antwoord 8

Zie de bovenstaande definitie waarin staat dat een callback-functie wordt doorgegeven aan een andere functie en op een gegeven moment wordt aangeroepen.

In C++ is het wenselijk om callback-functies een klassenmethode te laten aanroepen. Wanneer u dit doet heeft u toegang tot de ledengegevens. Als u de C-manier gebruikt om een callback te definiëren, moet u deze naar een statische lidfunctie verwijzen. Dit is niet erg wenselijk.

Hier leest u hoe u callbacks kunt gebruiken in C++. Stel 4 bestanden. Een paar .CPP/.H-bestanden voor elke klasse. Klasse C1 is de klasse met een methode die we willen terugbellen. C2 roept terug naar de methode van C1. In dit voorbeeld heeft de callback-functie 1 parameter die ik heb toegevoegd omwille van de lezers. Het voorbeeld laat geen objecten zien die worden geïnstantieerd en gebruikt. Een use case voor deze implementatie is wanneer u één klasse hebt die gegevens leest en opslaat in tijdelijke ruimte en een andere die de gegevens nabewerkingen uitvoert. Met een callback-functie kan de callback voor elke rij data die wordt uitgelezen deze vervolgens verwerken. Met deze techniek wordt de overhead van de benodigde tijdelijke ruimte weggenomen. Het is vooral handig voor SQL-query’s die een grote hoeveelheid gegevens retourneren die vervolgens moeten worden nabewerkt.

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

Antwoord 9

Het geaccepteerde antwoord is veelomvattend, maar heeft betrekking op de vraag. Ik wil hier slechts een eenvoudig voorbeeld geven. Ik had een code die ik lang geleden had geschreven. ik wilde een boom doorkruisen met de juiste weg (linkerknooppunt, dan hoofdknooppunt en vervolgens rechterknooppunt) en wanneer ik naar een knooppunt reikte, wilde ik een willekeurige functie kunnen aanroepen zodat deze alles zou kunnen doen.

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

Antwoord 10

Met

Boost’s signals2kunt u zich abonneren op generieke ledenfuncties (zonder sjablonen!) en op een threadveilige manier.

Voorbeeld: Document-View-signalen kunnen worden gebruikt om flexibel te implementeren
Document-View architecturen. Het document zal een signaal bevatten om:
die elk van de weergaven kan verbinden. De volgende documentklasse
definieert een eenvoudig tekstdocument dat meerdere weergaven ondersteunt. Let daar op
het slaat een enkel signaal op waarmee alle weergaven worden verbonden.

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

Vervolgens kunnen we beginnen met het definiëren van weergaven. De volgende TextView-klasse
biedt een eenvoudige weergave van de documenttekst.

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

Other episodes