回答:
注:ほとんどの回答は、C ++で「コールバック」ロジックを実現するための1つの可能性である関数ポインターをカバーしていますが、今日のところ、私は最も好ましいものではないと思います。
コールバックは、クラスまたは関数によって受け入れられる呼び出し可能(詳細は下を参照)であり、そのコールバックに応じて現在のロジックをカスタマイズするために使用されます。
コールバックを使用する理由の1つは、呼び出された関数のロジックに依存せず、さまざまなコールバックで再利用できる汎用コードを記述することです。
標準アルゴリズムライブラリの多くの関数は<algorithm>
コールバックを使用します。たとえば、for_each
アルゴリズムは、一連のイテレータのすべてのアイテムに単項コールバックを適用します。
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
これは、最初にインクリメントし、次に適切な呼び出し可能オブジェクトを渡すことでベクトルを出力するために使用できます。次に例を示します。
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 << " "; });
印刷する
5 6.2 8 9.5 11.2
コールバックの別のアプリケーションは、特定のイベントの呼び出し元への通知であり、これにより、一定の静的/コンパイル時間の柔軟性が可能になります。
個人的には、2つの異なるコールバックを使用するローカル最適化ライブラリを使用しています。
したがって、ライブラリデザイナーは、通知コールバックを介してプログラマーに提供される情報で何が起こるかを決定する責任を負いません。関数の値は、ロジックコールバックによって提供されるため、実際にどのように決定するかについて心配する必要はありません。これらのことを正しく行うことは、ライブラリユーザーにとっての仕事であり、ライブラリをスリムでより一般的なものに保ちます。
さらに、コールバックは動的なランタイム動作を有効にすることができます。
ユーザーがキーボードのボタンを押すたびに実行される関数と、ゲームの動作を制御する一連の関数がある、ある種のゲームエンジンクラスを想像してみてください。コールバックを使用すると、実行時に実行するアクションを(再)決定できます。
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;
}
};
ここでは、関数key_pressed
はに格納されactions
ているコールバックを使用して、特定のキーが押されたときに目的の動作を取得します。プレーヤーがジャンプ用のボタンを変更することを選択した場合、エンジンは
game_core_instance.update_keybind(newly_selected_key, &player_jump);
したがって、次回このボタンがゲーム内で押されると、呼び出しの動作をkey_pressed
(呼び出しplayer_jump
)に変更します。
より正式な説明については、C ++の概念: cppreferenceで呼び出し可能を参照してください。
コールバック機能は、C ++(11)ではいくつかの方法で実現できます。これは、いくつかの異なることが呼び出し可能であることが判明しているためです*:
std::function
オブジェクトoperator()
)* 注:データメンバーへのポインターも呼び出すことができますが、関数はまったく呼び出されません。
注:C ++ 17以降では、メンバーケースへのポインターも処理するような呼び出しをf(...)
記述できますstd::invoke(f, ...)
。
関数ポインターは、コールバックが持つことができる「最も単純な」(一般性の点で、読みやすさの点では間違いなく最悪)タイプです。
簡単な関数を見てみましょうfoo
:
int foo (int x) { return 2+x; }
関数ポインタ型は、表記を有します
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
どこという名前の関数ポインタ型は次のようになります
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;
using
以来宣言は、私たちに物事が少し読みやすくするためのオプションを与えるtypedef
ためにf_int_t
:缶は次のように書くことも
using f_int_t = int(*)(int);
(少なくとも私にとっては)f_int_t
新しい型エイリアスであることが明確であり、関数ポインター型の認識もより簡単です
また、関数ポインタ型のコールバックを使用する関数の宣言は次のようになります。
// 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);
呼び出し表記は、単純な関数呼び出し構文に従います。
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
}
関数ポインタを使用するコールバック関数は、関数ポインタを使用して呼び出すことができます。
関数ポインタコールバックを受け取る関数を使用するのはかなり簡単です。
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
コールバックの動作に依存しない関数を記述できます。
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
可能なコールバックは
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
のように使用
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};
(一部のクラスのC
)メンバー関数へのポインターは、C
操作する型のオブジェクトを必要とする特別なタイプの(そしてさらに複雑な)関数ポインターです。
struct C
{
int y;
int foo(int x) const { return x+y; }
};
一部のクラスのメンバー関数型へのポインターにT
は、表記法があります
// 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)
メンバー関数への名前付きポインターは、関数ポインターと同様に、次のようになります。
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;
例:メンバー関数コールバックへのポインターを引数の1つとして取る関数の宣言:
// 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);
のメンバー関数へのポインターは、逆参照されたポインターでメンバーアクセス操作を使用C
することにより、タイプのオブジェクトに関して呼び出すことがC
できます。
注:括弧が必要です!
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
}
注:へのポインターC
が使用可能な場合、構文は同等です(ポインターへのポインターC
も逆参照する必要があります)。
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);
}
classのメンバー関数ポインターをT
使用するコールバック関数は、classのメンバー関数ポインターを使用して呼び出すことができますT
。
メンバー関数コールバックへのポインターを取る関数を使用することも、関数ポインターと同様に、非常に簡単です。
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
オブジェクト(ヘッダー<functional>
)このstd::function
クラスは、呼び出し可能オブジェクトを格納、コピー、または呼び出すための多態性関数ラッパーです。
std::function
オブジェクト/タイプ表記の記述std::function
呼び出し可能オブジェクトを格納するオブジェクトのタイプは次のようになります。
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
はoperator()
、そのターゲットを呼び出すために使用できるものを定義しました。
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
}
std::function
異なるタイプが渡され、暗黙的に変換することができるので、コールバック関数のポインタまたはメンバ関数へのポインタよりもより一般的であるstd::function
オブジェクト。
3.3.1関数ポインターとメンバー関数へのポインター
関数ポインタ
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
またはメンバー関数へのポインター
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
に使える。
3.3.2ラムダ式
ラムダ式からの名前のないクロージャはstd::function
オブジェクトに保存できます:
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
式
式の結果をstd::bind
渡すことができます。たとえば、パラメーターを関数ポインター呼び出しにバインドすることによって:
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 )
メンバー関数へのポインターを呼び出すためのオブジェクトとしてオブジェクトをバインドすることもできます。
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関数オブジェクト
適切なoperator()
オーバーロードを持つクラスのオブジェクトも、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 )
使用する関数ポインターの例を変更する 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]);
}
}
(3.3を参照)私たちはそれを使用する可能性が高いので、その関数にずっと多くのユーティリティを与えます:
// 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};
テンプレートを使用すると、コールバックを呼び出すコードは、std::function
オブジェクトを使用するよりもさらに一般的になる可能性があります。
テンプレートはコンパイル時の機能であり、コンパイル時のポリモーフィズムのための設計ツールであることに注意してください。ランタイムダイナミック動作がコールバックを通じて実現される場合、テンプレートは役立ちますが、ランタイムダイナミックを誘発しません。
一般化、つまり上記のstd_ftransform_every_int
コードは、テンプレートを使用することでさらに実現できます。
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]);
}
}
コールバックタイプのさらに一般的な(そして最も簡単な)構文は、単純な、推定されるテンプレート化された引数です。
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]);
}
}
注:含まれている出力には、テンプレート化されたタイプについて推定されたタイプ名が印刷されF
ます。の実装はtype_name
この投稿の最後に記載されています。
範囲の単項変換の最も一般的な実装は、標準ライブラリの一部、つまりstd::transform
、反復型に関してテンプレート化されているです。
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;
}
テンプレート化されたstd::function
コールバックメソッドの互換性のある型は、stdf_transform_every_int_templ
上記の型と同じです(3.4を参照)。
ただし、テンプレートバージョンを使用すると、使用されるコールバックのシグネチャが少し変わる場合があります。
// 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);
注:(std_ftransform_every_int
テンプレート化されてfoo
いないバージョン。上記を参照)はで機能しますが、を使用しません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";
}
のプレーンテンプレートパラメータは、transform_every_int_templ
呼び出し可能なすべてのタイプにすることができます。
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);
上記のコードは以下を出力します:
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
上記で使用した実装#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
、これはタイプミスですよね?foo
これがAFAIKを機能させるためのポインタである必要があります。
[conv.func]
C ++ 11標準の言う:「関数型Tの左辺値は、「ポインタへのT」型のprvalueに変換できます。結果は関数へのポインタです。「これは標準の変換であり、暗黙的に発生します。ここでは(もちろん)関数ポインタを使用できます。
コールバックを行うCの方法もあります。関数ポインター
//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);
}
ここで、クラスメソッドをコールバックとして渡したい場合、それらの関数ポインターへの宣言には、より複雑な宣言があります。例:
//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
コールバックタイプを使用せずにそれを行うことができますか?可能ですか?
typedef
読みやすくするための構文上の砂糖です。がなければtypedef
、関数ポインターのDoWorkObjectの定義は次のようになりますvoid DoWorkObject(int (*callback)(float))
。メンバーへのポインタは次のようになります。void DoWorkObject(int (ClassName::*callback)(float))
スコット・マイヤーズは良い例を挙げています:
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;
};
例はそれをすべて言うと思います。
std::function<>
C ++コールバックを記述する「モダンな」方法です。
コールバック関数ルーチンに渡され、それが渡されたルーチンによっていくつかの点で呼び出されるメソッドです。
これは、再利用可能なソフトウェアを作成するのに非常に役立ちます。たとえば、多くのオペレーティングシステムAPI(Windows APIなど)はコールバックを頻繁に使用します。
たとえば、フォルダ内のファイルを操作する場合、独自のルーチンを使用してAPI関数を呼び出すことができ、ルーチンは指定されたフォルダ内のファイルごとに1回実行されます。これにより、APIは非常に柔軟になります。
受け入れられた答えは非常に有用で、非常に包括的です。ただし、OPは
コールバック関数を書くための簡単な例を見たいのですが。
さあ、C ++ 11からstd::function
は、関数ポインタなどの必要はありません。
#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;
}
この例は、どういうわけか現実的print_hashes
です。ハッシュ関数の異なる実装で関数を呼び出したいので、この目的のために簡単なものを提供しました。文字列を受け取り、int(提供された文字列のハッシュ値)を返します。構文部分から覚えておく必要がstd::function<int (const std::string&)>
あるのは、そのような関数を、それを呼び出す関数の入力引数として説明することだけです。
C ++には、コールバック関数の明確な概念はありません。コールバックメカニズムは、多くの場合、関数ポインター、ファンクターオブジェクト、またはコールバックオブジェクトを介して実装されます。プログラマーは、コールバック機能を明示的に設計および実装する必要があります。
フィードバックに基づいて編集:
この回答は否定的なフィードバックを受け取っていますが、間違いではありません。私はどこから来たのかを説明するより良い仕事をするように努めます。
CおよびC ++には、コールバック関数を実装するために必要なものがすべて揃っています。コールバック関数を実装する最も一般的で簡単な方法は、関数の引数として関数ポインターを渡すことです。
ただし、コールバック関数と関数ポインターは同義ではありません。関数ポインタは言語メカニズムであり、コールバック関数はセマンティックな概念です。関数ポインターは、コールバック関数を実装する唯一の方法ではありません。ファンクターや、さまざまな仮想関数を使用することもできます。関数呼び出しをコールバックにするのは、関数を識別して呼び出すために使用されるメカニズムではなく、呼び出しのコンテキストとセマンティクスです。何かがコールバック関数であると言うことは、呼び出し元の関数と呼び出される特定の関数との間の通常の分離よりも大きい分離を意味し、呼び出し元と呼び出し先の間の概念的な結合が緩く、呼び出し元は呼び出されるものを明示的に制御します
たとえば、IFormatProviderの.NETドキュメントには、「GetFormat is a callback method」と書かれていますが、これはありふれたインターフェイスメソッドです。すべての仮想メソッド呼び出しがコールバック関数であると主張する人はいないと思います。GetFormatをコールバックメソッドにするのは、それが渡される方法や呼び出される方法の仕組みではなく、どのオブジェクトのGetFormatメソッドが呼び出されるかを選択する呼び出し元のセマンティクスです。
一部の言語には、通常はイベントとイベント処理に関連する、明示的なコールバックセマンティクスを持つ機能が含まれています。たとえば、C#には、コールバックの概念を中心に明示的に設計された構文とセマンティクスを持つイベントタイプがあります。Visual BasicにはHandles句があり、デリゲートまたは関数ポインターの概念を抽象化しながら、メソッドをコールバック関数として明示的に宣言します。これらの場合、コールバックの意味論的概念は言語自体に統合されています。
一方、CおよびC ++は、コールバック関数のセマンティックな概念をほとんど明示的に組み込みません。メカニズムはありますが、統合されたセマンティクスはありません。コールバック関数は問題なく実装できますが、明示的なコールバックセマンティクスを含むより洗練されたものを取得するには、QtがSignalsとSlotsで行ったことなど、C ++が提供するものの上に構築する必要があります。
簡単に言うと、C ++にはコールバックを実装するために必要なものがあり、多くの場合、関数ポインターを使用して非常に簡単かつ簡単にできます。それが持っていないものは、セマンティクスがコールバックに固有のキーワードと機能です(raise、emit、Handles、event + =など)。これらのタイプの要素を持つ言語から来ている場合、C ++のネイティブコールバックサポート去勢を感じるでしょう。
コールバック関数はC標準の一部であり、したがってC ++の一部でもあります。ただし、C ++を使用している場合は、代わりにオブザーバーパターンを使用することをお勧めします。http://en.wikipedia.org/wiki/Observer_pattern
コールバック関数が他の関数に渡され、ある時点で呼び出されると述べている上記の定義を参照してください。
C ++では、コールバック関数がクラスメソッドを呼び出すことが望ましいです。これを行うと、メンバーデータにアクセスできます。コールバックを定義するCの方法を使用する場合は、それを静的メンバー関数にポイントする必要があります。これはあまり望ましくありません。
C ++でコールバックを使用する方法は次のとおりです。4つのファイルを想定します。各クラスの.CPP / .Hファイルのペア。クラスC1は、コールバックしたいメソッドを持つクラスです。C2はC1のメソッドを呼び出します。この例では、コールバック関数は、読者のために追加した1つのパラメーターを取ります。この例では、インスタンス化されて使用されているオブジェクトは示されていません。この実装の1つの使用例は、データを読み取り、一時スペースに格納するクラスと、データを後処理するクラスがある場合です。コールバック関数を使用すると、データのすべての行の読み取りに対して、コールバックはそれを処理できます。この手法により、必要な一時スペースのオーバーヘッドが削減されます。これは、大量の後処理が必要な大量のデータを返すSQLクエリに特に役立ちます。
/////////////////////////////////////////////////////////////////////
// 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のsignals2を使用すると、汎用のメンバー関数(テンプレートなし!)をスレッドセーフな方法でサブスクライブできます。
例:ドキュメントビュー信号を使用して、柔軟なドキュメントビューアーキテクチャを実装できます。ドキュメントには、各ビューが接続できる信号が含まれます。次のDocumentクラスは、複数のビューをサポートする単純なテキストドキュメントを定義します。すべてのビューが接続される単一の信号を保存することに注意してください。
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;
};
次に、ビューの定義を開始できます。次のTextViewクラスは、ドキュメントテキストの単純なビューを提供します。
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;
};
受け入れられた答えは包括的ですが、質問に関連していますが、ここに簡単な例を示します。ずっと前に書いたコードがあった。私はツリーを順番に(左ノード、ルートノード、右ノード)トラバースしたいと思い、1つのノードに到達するたびに任意の関数を呼び出してすべてを実行できるようにしたいと考えました。
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);
}