Risposte:
Nota: la maggior parte delle risposte riguarda i puntatori a funzione che è una possibilità per ottenere la logica di "callback" in C ++, ma ad oggi non è la più favorevole che penso.
Un callback è un callable (vedi più avanti) accettato da una classe o funzione, usato per personalizzare la logica corrente in base a quel callback.
Un motivo per utilizzare i callback è scrivere codice generico che è indipendente dalla logica nella funzione chiamata e che può essere riutilizzato con callback diversi.
Molte funzioni della libreria di algoritmi standard <algorithm>
utilizzano i callback. Ad esempio, l' for_each
algoritmo applica un callback unario a ogni elemento in un intervallo di iteratori:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
che può essere utilizzato per incrementare prima e quindi stampare un vettore passando ad esempio callable appropriati:
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 << " "; });
quale stampa
5 6.2 8 9.5 11.2
Un'altra applicazione dei callback è la notifica ai chiamanti di determinati eventi che consente una certa flessibilità di tempo statico / di compilazione.
Personalmente, utilizzo una libreria di ottimizzazione locale che utilizza due callback diversi:
Pertanto, il progettista della biblioteca non ha il compito di decidere cosa succede con le informazioni fornite al programmatore tramite il callback di notifica e non deve preoccuparsi di come determinare effettivamente i valori delle funzioni perché sono forniti dal callback logico. Far funzionare bene queste cose è un compito per l'utente della biblioteca e mantiene la biblioteca sottile e più generica.
Inoltre, i callback possono abilitare il comportamento di runtime dinamico.
Immagina una sorta di classe del motore di gioco che ha una funzione che viene attivata, ogni volta che gli utenti premono un pulsante sulla sua tastiera e una serie di funzioni che controllano il comportamento del tuo gioco. Con i callback è possibile (ri) decidere in fase di esecuzione quale azione verrà intrapresa.
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;
}
};
Qui la funzione key_pressed
utilizza i callback memorizzati actions
per ottenere il comportamento desiderato quando viene premuto un determinato tasto. Se il giocatore sceglie di cambiare il pulsante per saltare, il motore può chiamare
game_core_instance.update_keybind(newly_selected_key, &player_jump);
e quindi modificare il comportamento di una chiamata key_pressed
(a cui le chiamate player_jump
) una volta che si preme questo pulsante la prossima volta in gioco.
Vedi concetti C ++: richiamabile su cppreference per una descrizione più formale.
La funzionalità di callback può essere realizzata in diversi modi in C ++ (11) poiché diverse cose risultano essere richiamabili * :
std::function
oggettioperator()
)* Nota: anche il puntatore ai membri dei dati può essere richiamato, ma nessuna funzione viene richiamata.
Nota: A partire da C ++ 17, è f(...)
possibile scrivere una chiamata come std::invoke(f, ...)
che gestisce anche il puntatore al caso membro.
Un puntatore a funzione è il tipo "più semplice" (in termini di generalità; in termini di leggibilità probabilmente il peggiore) che un callback può avere.
Diamo una funzione semplice foo
:
int foo (int x) { return 2+x; }
Un tipo di puntatore a funzione ha la notazione
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
dove apparirà un tipo di puntatore a funzione denominata
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;
La using
dichiarazione ci dà la possibilità di rendere le cose un po 'più leggibili, poiché il typedef
for f_int_t
può anche essere scritto come:
using f_int_t = int(*)(int);
Dove (almeno per me) è più chiaro che f_int_t
è il nuovo tipo alias e anche il riconoscimento del tipo di puntatore a funzione è più semplice
E una dichiarazione di una funzione che utilizza un callback del tipo di puntatore a funzione sarà:
// 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);
La notazione di chiamata segue la semplice sintassi della chiamata di funzione:
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
}
Una funzione di richiamata che utilizza un puntatore a funzione può essere chiamata utilizzando i puntatori a funzione.
L'uso di una funzione che accetta un callback del puntatore a funzione è piuttosto semplice:
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
È possibile scrivere una funzione che non si basa su come funziona il callback:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
dove possibili callbacks potrebbero essere
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
usato come
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};
Un puntatore alla funzione membro (di qualche classe C
) è un tipo speciale di (e ancora più complesso) puntatore a funzione che richiede un oggetto di tipo C
su cui operare.
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Un puntatore al tipo di funzione membro per alcune classi T
ha la notazione
// 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)
dove un puntatore denominato alla funzione membro apparirà in analogia al puntatore funzione :
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;
Esempio: dichiarazione di una funzione che prende un puntatore al callback della funzione membro come uno dei suoi argomenti:
// 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);
Il puntatore alla funzione membro di C
può essere richiamato, rispetto a un oggetto di tipo C
, usando le operazioni di accesso del membro sul puntatore senza riferimenti.
Nota: parentesi richiesta!
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
}
Nota: se C
è disponibile un puntatore a, la sintassi è equivalente (dove anche il puntatore C
deve essere referenziato):
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);
}
Una funzione di callback che prende un puntatore della funzione membro della classe T
può essere chiamata usando un puntatore della funzione membro della classe T
.
L'uso di una funzione che porta un puntatore al callback della funzione membro è, in analogia con i puntatori funzione, anche abbastanza semplice:
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
std::function
oggetti (intestazione <functional>
)La std::function
classe è un wrapper di funzione polimorfica per archiviare, copiare o invocare callable.
std::function
notazione oggetto / tipoIl tipo di un std::function
oggetto che memorizza un callable è simile a:
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;
È std::function
stata operator()
definita la classe che può essere utilizzata per invocare il suo obiettivo.
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
}
Il std::function
callback è più generico dei puntatori di funzione o del puntatore alla funzione membro poiché è possibile passare tipi diversi e convertirli implicitamente in un std::function
oggetto.
3.3.1 Puntatori di funzioni e puntatori a funzioni membro
Un puntatore a funzione
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
o un puntatore alla funzione membro
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
può essere utilizzato.
3.3.2 Espressioni lambda
Una chiusura senza nome da un'espressione lambda può essere memorizzata in un std::function
oggetto:
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
espressioni
È std::bind
possibile passare il risultato di un'espressione. Ad esempio associando i parametri a una chiamata del puntatore a funzione:
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 )
Dove anche gli oggetti possono essere associati come oggetto per l'invocazione del puntatore alle funzioni membro:
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 Oggetti funzione
Anche oggetti di classi con un operator()
sovraccarico adeguato possono essere memorizzati all'interno di un std::function
oggetto.
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 )
Modifica dell'esempio del puntatore a funzione da utilizzare 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]);
}
}
dà molta più utilità a quella funzione perché (vedi 3.3) abbiamo più possibilità di usarla:
// 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};
Utilizzando i modelli, il codice che chiama il callback può essere persino più generale dell'uso degli std::function
oggetti.
Si noti che i modelli sono una funzionalità di compilazione e sono uno strumento di progettazione per il polimorfismo in fase di compilazione. Se il comportamento dinamico di runtime deve essere ottenuto tramite callback, i template aiuteranno ma non indurranno la dinamica di runtime.
La generalizzazione, ad esempio, del std_ftransform_every_int
codice dall'alto ancora può essere ottenuta usando i template:
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]);
}
}
con una sintassi ancora più generale (e anche la più semplice) per un tipo di callback che è un argomento semplice, da dedurre:
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]);
}
}
Nota: l'output incluso stampa il nome del tipo dedotto per il tipo di modello F
. L'implementazione di type_name
è data alla fine di questo post.
L'implementazione più generale per la trasformazione unaria di un intervallo fa parte della libreria standard, vale a dire std::transform
, che è anche modellata rispetto ai tipi iterati.
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;
}
I tipi compatibili per il std::function
metodo di callback basato su modelli stdf_transform_every_int_templ
sono identici ai tipi sopra menzionati (vedere 3.4).
Usando la versione con template tuttavia, la firma del callback usato potrebbe cambiare leggermente:
// 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);
Nota: std_ftransform_every_int
(versione non basata su modelli; vedi sopra) funziona con foo
ma non utilizza 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";
}
Il semplice parametro di modello transform_every_int_templ
può essere ogni possibile tipo richiamabile.
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);
Il codice sopra riportato stampa:
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
implementazione utilizzata sopra#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;
}
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, questo è un errore di battitura giusto? foo
dovrebbe essere un puntatore affinché questo funzioni AFAIK.
[conv.func]
dello standard C ++ 11 dice: " Un valore di tipo T di funzione può essere convertito in un valore di tipo" puntatore a T. " Il risultato è un puntatore alla funzione. "Questa è una conversione standard e come tale avviene implicitamente. Si potrebbe (ovviamente) usare qui il puntatore a funzione.
Esiste anche il modo C di eseguire callback: puntatori a funzioni
//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);
}
Ora, se si desidera passare a metodi di classe come callback, le dichiarazioni a tali puntatori a funzioni hanno dichiarazioni più complesse, ad esempio:
//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);
}
typedef
il tipo di callback? È anche possibile?
typedef
è solo zucchero sintattico per renderlo più leggibile. Senza typedef
, la definizione di DoWorkObject per puntatori a funzione potrebbe essere: void DoWorkObject(int (*callback)(float))
. I consigli dei membri sarebbero:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers fa un bell'esempio:
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;
};
Penso che l'esempio dica tutto.
std::function<>
è il modo "moderno" di scrivere callback C ++.
Una funzione di callback è un metodo passato in una routine e chiamato ad un certo punto dalla routine a cui è passato.
Questo è molto utile per creare software riutilizzabili. Ad esempio, molte API del sistema operativo (come l'API di Windows) utilizzano fortemente i callback.
Ad esempio, se si desidera lavorare con i file in una cartella, è possibile chiamare una funzione API, con la propria routine, e la routine viene eseguita una volta per file nella cartella specificata. Ciò consente all'API di essere molto flessibile.
La risposta accettata è molto utile e abbastanza completa. Tuttavia, l'OP afferma
Vorrei vedere un semplice esempio per scrivere una funzione di callback.
Quindi ecco qua, da C ++ 11 hai std::function
quindi non c'è bisogno di puntatori di funzioni e cose simili:
#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;
}
Questo esempio è in qualche modo reale, perché si desidera chiamare la funzione print_hashes
con diverse implementazioni di funzioni hash, a questo scopo ne ho fornito uno semplice. Riceve una stringa, restituisce un int (un valore hash della stringa fornita) e tutto ciò che è necessario ricordare dalla parte della sintassi è std::function<int (const std::string&)>
che descrive tale funzione come argomento di input della funzione che lo invocherà.
Non esiste un concetto esplicito di una funzione di callback in C ++. I meccanismi di callback sono spesso implementati tramite puntatori a funzione, oggetti functor o oggetti callback. I programmatori devono progettare e implementare esplicitamente la funzionalità di callback.
Modifica in base al feedback:
Nonostante il feedback negativo che questa risposta ha ricevuto, non è sbagliata. Proverò a fare un lavoro migliore spiegando da dove vengo.
C e C ++ hanno tutto il necessario per implementare le funzioni di callback. Il modo più comune e banale per implementare una funzione di callback è passare un puntatore a funzione come argomento di funzione.
Tuttavia, le funzioni di callback e gli indicatori di funzione non sono sinonimi. Un puntatore a funzione è un meccanismo linguistico, mentre una funzione di callback è un concetto semantico. I puntatori a funzione non sono l'unico modo per implementare una funzione di callback: puoi anche usare funzioni e persino funzioni virtuali di varietà da giardino. Ciò che rende una funzione una chiamata non è il meccanismo utilizzato per identificare e chiamare la funzione, ma il contesto e la semantica della chiamata. Dire che qualcosa è una funzione di callback implica una separazione maggiore della normale tra la funzione di chiamata e la funzione specifica chiamata, un accoppiamento concettuale più libero tra il chiamante e il chiamante, con il chiamante che ha un controllo esplicito su ciò che viene chiamato.
Ad esempio, la documentazione .NET per IFormatProvider afferma che "GetFormat è un metodo di callback" , anche se è solo un metodo di interfaccia run-of-the-mill. Non penso che qualcuno sosterrebbe che tutte le chiamate al metodo virtuale sono funzioni di callback. Ciò che rende GetFormat un metodo di callback non è la meccanica di come viene passato o invocato, ma la semantica del chiamante che sceglie quale metodo GetFormat dell'oggetto verrà chiamato.
Alcune lingue includono funzionalità con semantica di callback esplicita, in genere correlate agli eventi e alla gestione degli eventi. Ad esempio, C # ha il tipo di evento con sintassi e semantica esplicitamente progettato attorno al concetto di callback. Visual Basic ha la sua clausola Handles , che dichiara esplicitamente un metodo come una funzione di callback mentre sottrae il concetto di delegati o puntatori a funzioni. In questi casi, il concetto semantico di callback è integrato nella lingua stessa.
C e C ++, d'altra parte, non incorporano il concetto semantico di funzioni di callback in modo altrettanto esplicito. I meccanismi ci sono, la semantica integrata no. Puoi implementare bene le funzioni di callback, ma per ottenere qualcosa di più sofisticato che includa la semantica di callback esplicita devi costruirla sulla base di ciò che offre C ++, come quello che Qt ha fatto con i loro segnali e slot .
In breve, C ++ offre ciò di cui hai bisogno per implementare i callback, spesso abbastanza facilmente e banalmente usando i puntatori a funzione. Ciò che non ha sono parole chiave e caratteristiche la cui semantica è specifica per i callback, come raise , emit , Handles , event + = , ecc. Se provieni da una lingua con questi tipi di elementi, il supporto di callback nativo in C ++ si sentirà castrato.
Le funzioni di callback fanno parte dello standard C, quindi anche del C ++. Ma se stai lavorando con C ++, ti suggerirei invece di utilizzare il modello di osservatore : http://en.wikipedia.org/wiki/Observer_pattern
Vedi la definizione sopra dove afferma che una funzione di callback viene passata ad un'altra funzione e ad un certo punto viene chiamata.
In C ++ è auspicabile che le funzioni di callback chiamino un metodo di classi. Quando lo fai hai accesso ai dati dei membri. Se si utilizza il modo C per definire un callback, è necessario puntarlo a una funzione membro statica. Questo non è molto desiderabile.
Ecco come è possibile utilizzare i callback in C ++. Assumi 4 file. Una coppia di file .CPP / .H per ogni classe. La classe C1 è la classe con un metodo che vogliamo richiamare. C2 richiama il metodo di C1. In questo esempio la funzione di callback accetta 1 parametro che ho aggiunto per il bene dei lettori. L'esempio non mostra alcun oggetto istanziato e utilizzato. Un caso d'uso per questa implementazione è quando si dispone di una classe che legge e archivia i dati nello spazio temporaneo e un'altra che pubblica i dati. Con una funzione di callback, per ogni riga di dati letti il callback può quindi elaborarlo. Questa tecnica elimina le spese generali dello spazio temporaneo richiesto. È particolarmente utile per le query SQL che restituiscono una grande quantità di dati che devono essere successivamente elaborati.
/////////////////////////////////////////////////////////////////////
// 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);
}
Boost's signal2 ti consente di abbonare funzioni membro generiche (senza template!) E in modo sicuro.
Esempio: i segnali di Document-View possono essere utilizzati per implementare architetture flessibili di Document-View. Il documento conterrà un segnale a cui ciascuna vista può connettersi. La seguente classe Document definisce un semplice documento di testo che supporta viste multiple. Si noti che memorizza un singolo segnale a cui saranno collegate tutte le viste.
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;
};
Successivamente, possiamo iniziare a definire le viste. La seguente classe TextView fornisce una semplice visualizzazione del testo del documento.
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;
};
La risposta accettata è esaustiva ma collegata alla domanda, voglio solo fare un semplice esempio qui. Avevo un codice che avevo scritto molto tempo fa. volevo attraversare un albero in modo ordinato (nodo sinistro quindi nodo radice quindi nodo destro) e ogni volta che raggiungo un nodo volevo essere in grado di chiamare una funzione arbitraria in modo che potesse fare tutto.
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);
}