답변:
참고 : 대부분의 답변은 함수 포인터를 다루며 C ++에서 "콜백"논리를 달성 할 수있는 가능성은 있지만 오늘날 가장 좋은 것은 아닙니다.
콜백은 클래스 또는 함수에 의해 허용되는 콜 러블 (추가 참조)이며 해당 콜백에 따라 현재 로직을 사용자 정의하는 데 사용됩니다.
콜백을 사용하는 한 가지 이유 는 호출 된 함수의 논리와 독립적이며 다른 콜백과 함께 재사용 할 수있는 일반 코드 를 작성 하는 것입니다.
표준 알고리즘 라이브러리의 많은 함수는 <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
콜백의 또 다른 적용은 일정량의 정적 / 컴파일 시간 유연성을 가능하게하는 특정 이벤트를 호출자에게 알리는 것입니다.
개인적으로 두 가지 콜백을 사용하는 로컬 최적화 라이브러리를 사용합니다.
따라서 라이브러리 디자이너는 알림 콜백을 통해 프로그래머에게 제공되는 정보로 발생하는 정보를 결정할 책임이 없으며 로직 콜백에서 제공되므로 함수 값을 실제로 결정하는 방법에 대해 걱정할 필요가 없습니다. 이러한 것들을 올바르게 얻는 것은 라이브러리 사용자로 인한 작업이며 라이브러리를 얇고 일반적으로 유지합니다.
또한 콜백은 동적 런타임 동작을 가능하게합니다.
사용자가 키보드의 버튼과 게임 동작을 제어하는 일련의 기능을 누를 때마다 실행되는 기능이있는 일종의 게임 엔진 클래스를 상상해보십시오. 콜백을 사용하면 런타임에 수행 할 작업을 (다시) 결정할 수 있습니다.
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
for 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;
예 : 멤버 함수 콜백에 대한 포인터를 인수 중 하나로 사용 하는 함수 선언 :
// 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);
}
클래스 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
클래스 저장, 복사 또는 callables 호출 다형성 함수 래퍼이다.
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 표준의 @konoufo : 는 다음 과 같이 말합니다. " 함수 유형 T의 lvalue는"포인터에서 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))
Scott Meyers는 좋은 예입니다.
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 함수를 호출 할 수 있으며 지정된 폴더의 파일마다 루틴이 한 번 실행됩니다. 이를 통해 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 ++에는 명시적인 콜백 함수 개념이 없습니다. 콜백 메커니즘은 종종 함수 포인터, functor 객체 또는 콜백 객체를 통해 구현됩니다. 프로그래머는 명시 적으로 콜백 기능을 설계하고 구현해야합니다.
피드백을 기반으로 편집 :
이 답변에 대한 부정적인 피드백에도 불구하고 잘못된 것은 아닙니다. 나는 어디에서 왔는지 설명하는 더 나은 일을하려고 노력할 것이다.
C 및 C ++에는 콜백 함수를 구현하는 데 필요한 모든 것이 있습니다. 콜백 함수를 구현하는 가장 일반적이고 사소한 방법은 함수 포인터를 함수 인수로 전달하는 것입니다.
그러나 콜백 함수와 함수 포인터는 동의어가 아닙니다. 함수 포인터는 언어 메커니즘이며 콜백 함수는 의미 개념입니다. 함수 포인터는 콜백 함수를 구현할 수있는 유일한 방법은 아닙니다. 또한 펑터와 정원 다양한 가상 함수를 사용할 수도 있습니다. 함수 호출을 콜백으로 만드는 것은 함수를 식별하고 호출하는 데 사용되는 메커니즘이 아니라 호출의 컨텍스트 및 의미입니다. 콜백 함수라고 말하는 것은 호출 기능과 호출되는 특정 기능 사이의 정상적인 분리, 호출자와 호출 수신자 사이의 느슨한 개념적 결합, 호출자가 호출되는 것을 명시 적으로 제어 할 수 있음을 의미합니다.
예를 들어, IFormatProvider 의 .NET 설명서에 따르면 "GetFormat은 콜백 메서드" 라고 말하지만 방금 다룬 인터페이스 방법 일뿐입니다. 나는 모든 가상 메소드 호출이 콜백 함수라고 주장하는 사람은 없다고 생각합니다. GetFormat을 콜백 메소드로 만드는 것은 그것이 전달되거나 호출되는 방식의 메커니즘이 아니라 호출자의 시맨틱이 어떤 오브젝트의 GetFormat 메소드가 호출 될지를 선택하는 것입니다.
일부 언어에는 일반적으로 이벤트 및 이벤트 처리와 관련된 명시적인 콜백 시맨틱 기능이 있습니다. 예를 들어 C #에는 콜백 개념을 중심으로 명시 적으로 설계된 구문 및 의미 체계 가있는 이벤트 유형이 있습니다. Visual Basic에는 Handles 절이 있습니다.이 절은 대리자 또는 함수 포인터의 개념을 추상화하면서 메서드를 콜백 함수로 명시 적으로 선언합니다. 이 경우 콜백의 시맨틱 개념이 언어 자체에 통합됩니다.
반면에 C와 C ++ 는 콜백 함수 의 시맨틱 개념 을 거의 명시 적으로 포함하지 않습니다 . 메커니즘이 있으며 통합 의미론은 없습니다. 콜백 함수를 잘 구현할 수는 있지만 명시적인 콜백 의미를 포함하는 더 정교한 것을 얻으려면 Qt가 Signals and 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 개의 매개 변수를 사용합니다. 이 예제는 인스턴스화되고 사용되는 객체를 보여주지 않습니다. 이 구현의 사용 사례 중 하나는 데이터를 임시 공간으로 읽고 저장하는 클래스와 데이터를 처리하는 다른 클래스가있는 경우입니다. 콜백 함수를 사용하면 모든 데이터 행에 대해 콜백을 읽고 처리 할 수 있습니다. 이 기술은 필요한 임시 공간의 오버 헤드를 줄입니다. 많은 양의 데이터를 반환 한 후 사후 처리해야하는 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의 신호 2를 사용하면 일반적인 멤버 함수 (템플릿없이)를 스레드 안전 방식으로 구독 할 수 있습니다.
예 : 문서보기 신호를 사용하여 유연한 문서보기 아키텍처를 구현할 수 있습니다. 문서에는 각보기가 연결할 수있는 신호가 포함됩니다. 다음 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;
};
허용 된 답변은 포괄적이지만 여기서는 간단한 예를 제시하려는 질문과 관련이 있습니다. 오래 전에 작성한 코드가 있습니다. 나는 순서대로 (왼쪽 노드 다음 루트 노드 다음 오른쪽 노드) 트리를 통과하고 싶었고 한 노드에 도달 할 때마다 모든 기능을 수행 할 수 있도록 임의의 함수를 호출 할 수 있기를 원했습니다.
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);
}