Câu trả lời:
Lưu ý: Hầu hết các câu trả lời bao gồm các con trỏ hàm có khả năng đạt được logic "gọi lại" trong C ++, nhưng cho đến ngày nay không phải là câu hỏi thuận lợi nhất tôi nghĩ.
Một cuộc gọi lại là một cuộc gọi (xem thêm xuống) được chấp nhận bởi một lớp hoặc chức năng, được sử dụng để tùy chỉnh logic hiện tại tùy thuộc vào cuộc gọi lại đó.
Một lý do để sử dụng gọi lại là để viết chung mã không phụ thuộc vào logic trong hàm được gọi và có thể được sử dụng lại với các cuộc gọi lại khác nhau.
Nhiều chức năng của thư viện thuật toán tiêu chuẩn <algorithm>
sử dụng các cuộc gọi lại. Ví dụ: for_each
thuật toán áp dụng một cuộc gọi lại đơn phương cho mọi mục trong một phạm vi các vòng lặp:
template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
for (; first != last; ++first) {
f(*first);
}
return f;
}
có thể được sử dụng để tăng đầu tiên và sau đó in một vectơ bằng cách chuyển các tên gọi thích hợp chẳng hạn:
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 << " "; });
mà in
5 6.2 8 9.5 11.2
Một ứng dụng khác của cuộc gọi lại là thông báo cho người gọi về một số sự kiện nhất định cho phép một lượng linh hoạt thời gian tĩnh / biên dịch nhất định.
Cá nhân, tôi sử dụng thư viện tối ưu hóa cục bộ sử dụng hai cuộc gọi lại khác nhau:
Do đó, người thiết kế thư viện không chịu trách nhiệm quyết định những gì xảy ra với thông tin được cung cấp cho lập trình viên thông qua cuộc gọi lại thông báo và anh ta không cần lo lắng về cách xác định giá trị hàm thực sự bởi vì chúng được cung cấp bởi cuộc gọi lại logic. Làm cho những điều đó đúng là một nhiệm vụ do người dùng thư viện và giữ cho thư viện mỏng và chung chung hơn.
Hơn nữa, cuộc gọi lại có thể cho phép hành vi thời gian chạy động.
Hãy tưởng tượng một loại lớp công cụ trò chơi nào đó có chức năng được kích hoạt, mỗi lần người dùng nhấn một nút trên bàn phím và một bộ chức năng kiểm soát hành vi trò chơi của bạn. Với các cuộc gọi lại, bạn có thể (tái) quyết định trong thời gian chạy hành động nào sẽ được thực hiện.
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;
}
};
Ở đây, hàm key_pressed
sử dụng các hàm gọi lại được lưu trữ actions
để có được hành vi mong muốn khi nhấn một phím nhất định. Nếu người chơi chọn thay đổi nút để nhảy, động cơ có thể gọi
game_core_instance.update_keybind(newly_selected_key, &player_jump);
và do đó thay đổi hành vi của một cuộc gọi đến key_pressed
(mà các cuộc gọi player_jump
) một khi nút này được nhấn vào lần tiếp theo ingame.
Xem các khái niệm C ++: Có thể gọi được trên cppreference để có mô tả chính thức hơn.
Chức năng gọi lại có thể được nhận ra theo nhiều cách trong C ++ (11) do một số thứ khác nhau hóa ra có thể gọi được * :
std::function
các đối tượngoperator()
)* Lưu ý: Con trỏ tới các thành viên dữ liệu cũng có thể gọi được nhưng không có chức năng nào được gọi cả.
Lưu ý: Kể từ C ++ 17, một cuộc gọi như f(...)
có thể được viết vì std::invoke(f, ...)
nó cũng xử lý con trỏ tới trường hợp thành viên.
Một con trỏ hàm là 'đơn giản nhất' (về tính tổng quát; về khả năng đọc được cho là tệ nhất) loại gọi lại có thể có.
Chúng ta có một chức năng đơn giản foo
:
int foo (int x) { return 2+x; }
Một loại con trỏ hàm có ký hiệu
return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
trong đó một kiểu con trỏ hàm được đặt tên sẽ trông như thế nào
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;
Các using
tuyên bố cho chúng ta lựa chọn để làm cho mọi việc dễ đọc hơn một chút, kể từ khi typedef
cho f_int_t
cũng có thể được viết như sau:
using f_int_t = int(*)(int);
Trong đó (ít nhất là đối với tôi) thì rõ ràng hơn f_int_t
là bí danh kiểu mới và việc nhận dạng kiểu con trỏ hàm cũng dễ dàng hơn
Và một khai báo của một hàm sử dụng một hàm gọi lại của kiểu con trỏ hàm sẽ là:
// 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);
Ký hiệu cuộc gọi tuân theo cú pháp gọi hàm đơn giản:
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
}
Một hàm gọi lại lấy một con trỏ hàm có thể được gọi bằng cách sử dụng các con trỏ hàm.
Sử dụng một hàm có một hàm gọi lại con trỏ hàm khá đơn giản:
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
Một hàm ca được viết mà không dựa vào cách gọi lại hoạt động:
void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
for (unsigned i = 0; i < n; ++i)
{
v[i] = fp(v[i]);
}
}
nơi gọi lại có thể
int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
được sử dụng như
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};
Một con trỏ tới hàm thành viên (của một số lớp C
) là một loại con trỏ hàm đặc biệt (và thậm chí phức tạp hơn) đòi hỏi một đối tượng kiểu C
để hoạt động.
struct C
{
int y;
int foo(int x) const { return x+y; }
};
Một con trỏ tới kiểu hàm thành viên cho một số lớp T
có ký hiệu
// 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)
trong đó một con trỏ được đặt tên cho hàm thành viên sẽ - tương tự như con trỏ hàm - trông như thế này:
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;
Ví dụ: Khai báo một hàm lấy một con trỏ tới hàm gọi lại thành viên làm một trong các đối số của nó:
// 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);
Con trỏ tới hàm thành viên của C
có thể được gọi, đối với một đối tượng kiểu C
bằng cách sử dụng các hoạt động truy cập thành viên trên con trỏ bị hủy đăng ký.
Lưu ý: Yêu cầu dấu ngoặc đơn!
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
}
Lưu ý: Nếu một con trỏ C
có sẵn, cú pháp là tương đương (trong đó con trỏ C
phải được hủy đăng ký):
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);
}
Một hàm gọi lại lấy một con trỏ hàm thành viên của lớp T
có thể được gọi bằng cách sử dụng một con trỏ hàm thành viên của lớp T
.
Sử dụng một hàm đưa con trỏ đến hàm gọi lại thành viên là - tương tự như con trỏ hàm - cũng khá đơn giản:
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
đối tượng (tiêu đề <functional>
)Các std::function
lớp học là một chức năng wrapper đa hình để lưu trữ, sao chép hoặc invoke callables.
std::function
ký hiệu đối tượng / loạiLoại std::function
đối tượng lưu trữ một cuộc gọi có thể gọi là:
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;
Lớp std::function
đã operator()
định nghĩa có thể được sử dụng để gọi mục tiêu của nó.
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
}
Cuộc std::function
gọi lại chung chung hơn con trỏ hàm hoặc con trỏ đến hàm thành viên vì các loại khác nhau có thể được truyền và chuyển đổi hoàn toàn thành một std::function
đối tượng.
3.3.1 Con trỏ hàm và con trỏ tới hàm thành viên
Một con trỏ hàm
int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
hoặc một con trỏ tới hàm thành viên
int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
có thể được sử dụng.
3.3.2 Biểu thức Lambda
Một bao đóng không tên từ biểu thức lambda có thể được lưu trữ trong một std::function
đối tượng:
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
biểu thức
Kết quả của một std::bind
biểu thức có thể được thông qua. Ví dụ: bằng cách liên kết các tham số với một lệnh gọi con trỏ hàm:
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 )
Trường hợp các đối tượng cũng có thể được liên kết làm đối tượng cho việc gọi con trỏ tới các hàm thành viên:
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 Đối tượng chức năng
Các đối tượng của các lớp có operator()
quá tải thích hợp cũng có thể được lưu trữ bên trong một std::function
đối tượng.
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 )
Thay đổi ví dụ con trỏ hàm để sử dụng 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]);
}
}
cung cấp nhiều tiện ích hơn cho chức năng đó bởi vì (xem 3.3) chúng ta có nhiều khả năng sử dụng nó hơn:
// 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};
Sử dụng các mẫu, mã gọi lại gọi lại có thể còn chung chung hơn so với sử dụng std::function
các đối tượng.
Lưu ý rằng các mẫu là một tính năng thời gian biên dịch và là một công cụ thiết kế cho tính đa hình thời gian biên dịch. Nếu hành vi động thời gian chạy phải đạt được thông qua các cuộc gọi lại, các mẫu sẽ giúp nhưng chúng sẽ không tạo ra động lực thời gian chạy.
Tổng quát hóa tức là std_ftransform_every_int
mã từ phía trên thậm chí có thể đạt được bằng cách sử dụng các mẫu:
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]);
}
}
với cú pháp thậm chí tổng quát hơn (cũng như dễ nhất) cho loại gọi lại là một đối số khuôn mẫu đơn giản, dễ bị suy diễn:
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]);
}
}
Lưu ý: Đầu ra đi kèm in tên loại được suy ra cho loại templated F
. Việc thực hiện type_name
được đưa ra ở cuối bài này.
Việc triển khai chung nhất cho phép chuyển đổi đơn nhất của một phạm vi là một phần của thư viện chuẩn, cụ thể là std::transform
, cũng được đặt theo khuôn mẫu đối với các kiểu lặp.
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;
}
Các loại tương thích cho std::function
phương thức gọi lại templated stdf_transform_every_int_templ
giống hệt với các loại đã đề cập ở trên (xem 3.4).
Tuy nhiên, sử dụng phiên bản templated, chữ ký của cuộc gọi lại được sử dụng có thể thay đổi một chút:
// 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);
Lưu ý: std_ftransform_every_int
(phiên bản không có templated; xem ở trên) không hoạt động foo
nhưng không sử dụng 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";
}
Tham số templated đơn giản của transform_every_int_templ
có thể là mọi loại có thể gọi được.
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);
Các mã trên in:
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
thực hiện được sử dụng ở trên#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
, đây là một lỗi đánh máy phải không? foo
nên là một con trỏ để làm việc này AFAIK.
[conv.func]
của tiêu chuẩn C ++ 11 nói: " Một giá trị của loại chức năng T có thể được chuyển đổi thành một giá trị của con trỏ kiểu kiểu thành T. Kết quả là một con trỏ tới hàm. "Đây là một chuyển đổi tiêu chuẩn và như vậy xảy ra ngầm. Người ta có thể (tất nhiên) sử dụng con trỏ hàm ở đây.
Ngoài ra còn có cách thực hiện cuộc gọi lại C: con trỏ hàm
//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);
}
Bây giờ nếu bạn muốn truyền vào các phương thức lớp dưới dạng gọi lại, các khai báo cho các con trỏ hàm đó có các khai báo phức tạp hơn, ví dụ:
//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
loại gọi lại? Nó thậm chí có thể?
typedef
chỉ là cú pháp đường để làm cho nó dễ đọc hơn. Nếu không typedef
, định nghĩa của DoWorkObject cho các con trỏ hàm sẽ là : void DoWorkObject(int (*callback)(float))
. Đối với con trỏ thành viên sẽ là:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers đưa ra một ví dụ hay:
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;
};
Tôi nghĩ rằng ví dụ nói lên tất cả.
std::function<>
là cách viết "gọi lại" hiện đại của C ++.
Hàm gọi lại là một phương thức được truyền vào một thường trình và được gọi tại một số điểm theo thói quen mà nó được truyền vào.
Điều này rất hữu ích để làm phần mềm tái sử dụng. Ví dụ: nhiều API hệ điều hành (như API Windows) sử dụng các cuộc gọi lại rất nhiều.
Ví dụ: nếu bạn muốn làm việc với các tệp trong một thư mục - bạn có thể gọi hàm API, với thói quen của riêng bạn và thói quen của bạn được chạy một lần cho mỗi tệp trong thư mục được chỉ định. Điều này cho phép API rất linh hoạt.
Câu trả lời được chấp nhận là rất hữu ích và khá toàn diện. Tuy nhiên, OP tuyên bố
Tôi muốn xem một ví dụ đơn giản để viết hàm gọi lại.
Vì vậy, ở đây bạn đi, từ C ++ 11 bạn có std::function
nên không cần con trỏ hàm và các công cụ tương tự:
#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;
}
Ví dụ này là bằng cách nào đó thực tế, bởi vì bạn muốn gọi hàm print_hashes
với các triển khai khác nhau của hàm băm, vì mục đích này tôi đã cung cấp một hàm đơn giản. Nó nhận được một chuỗi, trả về một int (giá trị băm của chuỗi được cung cấp) và tất cả những gì bạn cần nhớ từ phần cú pháp là std::function<int (const std::string&)>
mô tả chức năng đó như là một đối số đầu vào của hàm sẽ gọi nó.
Không có khái niệm rõ ràng về chức năng gọi lại trong C ++. Các cơ chế gọi lại thường được thực hiện thông qua các con trỏ hàm, các đối tượng functor hoặc các đối tượng gọi lại. Các lập trình viên phải thiết kế rõ ràng và thực hiện chức năng gọi lại.
Chỉnh sửa dựa trên phản hồi:
Mặc dù phản hồi tiêu cực câu trả lời này đã nhận được, nó không sai. Tôi sẽ cố gắng làm tốt hơn việc giải thích nơi tôi đến.
C và C ++ có mọi thứ bạn cần để thực hiện các hàm gọi lại. Cách phổ biến và tầm thường nhất để thực hiện chức năng gọi lại là truyền con trỏ hàm làm đối số hàm.
Tuy nhiên, hàm gọi lại và con trỏ hàm không đồng nghĩa. Con trỏ hàm là một cơ chế ngôn ngữ, trong khi hàm gọi lại là một khái niệm ngữ nghĩa. Con trỏ hàm không phải là cách duy nhất để thực hiện chức năng gọi lại - bạn cũng có thể sử dụng hàm functor và thậm chí các hàm ảo khác nhau trong vườn. Điều làm cho một hàm gọi một cuộc gọi lại không phải là cơ chế được sử dụng để xác định và gọi hàm, mà là ngữ cảnh và ngữ nghĩa của cuộc gọi. Nói một cái gì đó là một hàm gọi lại ngụ ý một sự tách biệt lớn hơn bình thường giữa chức năng gọi và chức năng cụ thể được gọi, một khớp nối khái niệm lỏng lẻo hơn giữa người gọi và callee, với người gọi có quyền kiểm soát rõ ràng đối với những gì được gọi.
Ví dụ: tài liệu .NET cho IFormatProvider nói rằng "GetFormat là một phương thức gọi lại" , mặc dù nó chỉ là một phương thức giao diện chạy. Tôi không nghĩ ai sẽ tranh luận rằng tất cả các cuộc gọi phương thức ảo là các hàm gọi lại. Điều làm cho GetFormat trở thành một phương thức gọi lại không phải là cơ chế về cách nó được truyền hoặc gọi, mà là ngữ nghĩa của người gọi chọn phương thức GetFormat của đối tượng sẽ được gọi.
Một số ngôn ngữ bao gồm các tính năng với ngữ nghĩa gọi lại rõ ràng, thường liên quan đến các sự kiện và xử lý sự kiện. Ví dụ, C # có loại sự kiện với cú pháp và ngữ nghĩa được thiết kế rõ ràng xung quanh khái niệm gọi lại. Visual Basic có mệnh đề Handles , nó tuyên bố rõ ràng một phương thức là hàm gọi lại trong khi trừu tượng hóa khái niệm đại biểu hoặc con trỏ hàm. Trong những trường hợp này, khái niệm ngữ nghĩa của một cuộc gọi lại được tích hợp vào chính ngôn ngữ.
C và C ++, mặt khác, không nhúng khái niệm ngữ nghĩa của các hàm gọi lại gần như rõ ràng. Các cơ chế là có, ngữ nghĩa tích hợp thì không. Bạn có thể thực hiện các chức năng gọi lại tốt, nhưng để có được thứ gì đó tinh vi hơn bao gồm ngữ nghĩa gọi lại rõ ràng, bạn phải xây dựng nó dựa trên những gì C ++ cung cấp, chẳng hạn như những gì Qt đã làm với Tín hiệu và Slots của họ .
Tóm lại, C ++ có những gì bạn cần để thực hiện các cuộc gọi lại, thường khá dễ dàng và tầm thường khi sử dụng các hàm con trỏ hàm. Những gì nó không có là các từ khóa và tính năng có ngữ nghĩa cụ thể cho các cuộc gọi lại, chẳng hạn như nâng cao , phát ra , Xử lý , sự kiện + = , v.v. Nếu bạn đến từ một ngôn ngữ có các loại yếu tố đó, hỗ trợ gọi lại riêng trong C ++ sẽ cảm thấy trung tính.
Các hàm gọi lại là một phần của tiêu chuẩn C, do đó cũng là một phần của C ++. Nhưng nếu bạn đang làm việc với C ++, tôi khuyên bạn nên sử dụng mẫu người quan sát thay thế: http://en.wikipedia.org/wiki/Observer_potype
Xem định nghĩa ở trên trong đó nói rằng hàm gọi lại được chuyển sang một số hàm khác và tại một số điểm, nó được gọi.
Trong C ++, mong muốn có các hàm gọi lại gọi một phương thức lớp. Khi bạn làm điều này, bạn có quyền truy cập vào dữ liệu thành viên. Nếu bạn sử dụng cách C để xác định một cuộc gọi lại, bạn sẽ phải trỏ nó đến một hàm thành viên tĩnh. Điều này không phải là rất mong muốn.
Đây là cách bạn có thể sử dụng các cuộc gọi lại trong C ++. Giả sử 4 tập tin. Một cặp tệp .CPP / .H cho mỗi lớp. Lớp C1 là lớp có phương thức chúng ta muốn gọi lại. C2 gọi lại phương thức của C1. Trong ví dụ này, hàm gọi lại lấy 1 tham số mà tôi đã thêm cho người đọc vì lợi ích. Ví dụ này không hiển thị bất kỳ đối tượng nào được khởi tạo và sử dụng. Một trường hợp sử dụng cho việc triển khai này là khi bạn có một lớp đọc và lưu trữ dữ liệu vào không gian tạm thời và một lớp khác xử lý dữ liệu. Với chức năng gọi lại, đối với mỗi hàng dữ liệu đọc thì gọi lại có thể xử lý nó. Kỹ thuật này cắt giảm chi phí không gian tạm thời cần thiết. Nó đặc biệt hữu ích cho các truy vấn SQL trả về một lượng lớn dữ liệu mà sau đó phải được xử lý sau.
/////////////////////////////////////////////////////////////////////
// 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' signal2 cho phép bạn đăng ký các chức năng thành viên chung (không có mẫu!) Và theo cách an toàn cho chủ đề.
Ví dụ: Tín hiệu Chế độ xem Tài liệu có thể được sử dụng để triển khai các kiến trúc Chế độ xem Tài liệu linh hoạt. Tài liệu sẽ chứa một tín hiệu mà mỗi chế độ xem có thể kết nối. Lớp Tài liệu sau định nghĩa một tài liệu văn bản đơn giản hỗ trợ các khung nhìn mulitple. Lưu ý rằng nó lưu một tín hiệu duy nhất mà tất cả các chế độ xem sẽ được kết nối.
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;
};
Tiếp theo, chúng ta có thể bắt đầu xác định quan điểm. Lớp TextView sau đây cung cấp một khung nhìn đơn giản của văn bản tài liệu.
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;
};
Câu trả lời được chấp nhận là toàn diện nhưng liên quan đến câu hỏi tôi chỉ muốn đưa ra một ví dụ đơn giản ở đây. Tôi đã có một mã mà tôi đã viết nó từ lâu. tôi muốn đi ngang qua một cây theo cách theo thứ tự (nút trái rồi nút gốc rồi nút phải) và bất cứ khi nào tôi đạt đến một nút, tôi muốn có thể gọi một hàm tùy ý để nó có thể làm mọi thứ.
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);
}