问题
Please consider the following:
typedef int (*callback_function)(int a, int b);
class receiving_callbacks_class
{
public:
static register_callback(int iterator, callback_function fn)
{
function_list[iterator] = fn;
}
private:
static callback_function function_list[10];
}
This function_list
is used by a C library, so it can do call backs on certain events.
I now would like to add a routine to every callback that gets registered via this function. So that it gets called like this:
default_routine();
fn();
I tried doing it with templates, but I could not find a way to do that inside the register_callback
function. Every way to do it with templates meant wrapping the function to register before calling register_callback. I would like to contain this inside this particular class.
This differs from How to add standard routine to every function in array of function pointers? by adding the requirement that no changes can be made where register_callback
is called; all changes must be within receiving_callbacks_class
.
This is for an embedded system where I don't have the freedom to use std
. All functions passed to the register_class
function will be static.
Is this possible?
回答1:
First, I would like to add a preamble explaining why this is difficult in general. There is a problem involving the amount of information being stored in the C library. Other C callbacks systems store two pieces of information per callback: the address of the function to call and arbitrary data cast to void*
. The function is required to accept a void*
argument in addition to the arguments required by the nature of the callback. This "extra" argument receives the arbitrary data. Casting this back to its original type allows access to as much extra data as needed; it could be null if no extra data is needed. (In this case, it would be cast back to callback_function
.)
In the current context, however, the only information stored in the C library is the address of a function to call. This information must be different if different callbacks are to be invoked, hence there must be different functions. The requirement that the call site not be changed means that the different functions need to be provided by receiving_callbacks_class
, yet these functions need to adapt when new callbacks are written by users of receiving_callbacks_class
.
I can think of four main techniques for generating multiple similar functions in C++, but one of them uses macros, so let's call it three: lambdas, templates, and copy-paste. A lambda that captures the address of the real callback is no longer convertible to a function pointer, so no longer usable with the C library. A template would use the address of the real callback as the template parameter, which means that address would have to be a compile-time constant. However, C++ does not provide a way to require that a function argument be a compile-time constant, making the approach unsuitable for use inside register_callback()
. That leaves copy-paste, which normally is a pain, but might be acceptable when the number of callbacks is as small as 10.
I can base an approach on the definition of function_list
. There is a smallish limit on the number of callbacks passed to the C library and they can be invoked in constant time, given their index. This approach might be less appealing if the array is replaced by a container that does not support indexed access. It definitely becomes less appealing as the size of the container increases. It might be unusable if there is not a compile-time limit on the size of the container.
To receiving_callbacks_class
I might add two pieces of static data: a second array of function pointers, and a size for both of the arrays.
class receiving_callbacks_class
{
public:
// Always use a symbolic constant when a magic number is needed more than once.
static constexpr unsigned MAX_CALLBACKS = 10;
// Looks the same (but with a return type), and called the same as before.
static void register_callback(int iterator, callback_function fn)
{
function_list[iterator] = fn;
}
private:
// Old array using the new symbolic constant
static callback_function function_list[MAX_CALLBACKS];
// A new array, probably should be `const`.
static const callback_function wrapper_list[MAX_CALLBACKS];
};
The new array is for storing pointers to wrapper functions, functions that will call default_routine()
followed by the real callback. This array, not the old one, is what should be given to the C library. It still needs to be initialized, though. The initialization of the new array is where copy-paste comes in. I leave it to the reader to decide how large they would let MAX_CALLBACKS
get before this is considered unmanageable. Personally, I found the copy-paste to be reasonable when the size is 10.
// Initialize the new array
const callback_function receiving_callbacks_class::wrapper_list[MAX_CALLBACKS] = {
[](int a, int b) -> int { default_routine(); return function_list[0](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[1](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[2](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[3](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[4](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[5](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[6](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[7](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[8](a, b); },
[](int a, int b) -> int { default_routine(); return function_list[9](a, b); },
};
It might be appropriate to add a null check on the appropriate function_list
entry; I do not know how you handle having fewer than 10 callbacks.
Note that these lambdas are non-capturing, so they do convert to function pointers. They can be non-capturing only because their number is fixed at compile-time.
The last piece to change is what I mentioned before. The call into the C library would use the new array instead of the old. This was not included in the sample code, so I'll devise a name and parameter list.
//c_library(function_list, 10); // <-- replace this
c_library(wrapper_list, MAX_CALLBACKS); // <-- with this
Not my preferred setup, but it does meet the restrictions in the question.
来源:https://stackoverflow.com/questions/64519198/adding-a-standard-routine-to-function-pointer-passed-to-a-function