0

Suppose we have some kind of C style API like this:

 void register_callback(target, callback_function);

, where target is some kind of object, e.g. a server, and call_back is required to be a function pointer.

Now we want to adapt (more precisely, add a wrapper layer. We have no access to the underlying C code, as it is a third-party library) this API to C++. We want OOP, so we make the target into a class:

class Target {
  Target(): target() { register_callback(target, callback_function_member) }
  private:
    target_t target;
    void callback_function_member (param_t parameter) { /* a set of things to do with the members */ }
    // other members
}

We probably want to make the callback a member function as above, because it (in some cases) is closely tied with the instance. But this won't work. There is no way to adapt member function into a function pointer (see, e.g. Pointers to Member Functions).

And all solutions I found on the internet (including the previous one) only deals with the case where only one instance is responsible for the job. There we can specially write a wrapper function (see 1) or static function of a wrapper class.

The problem is, what if we want multiple instances, and furthermore, dynamic creation of them? You can write a wrapper function or class for each instance, like

Target *pTarget;
void wrapper(param_t parameter) {
  pTarget->callback_function_member(parameter);
}
int main() {
  Target myTarget();
  pTarget = &myTarget();
  myTarget.register(wrapper);
  // some work
  return 0;
}

But you cannot write a function manually for each instance that is dynamically created. And it does not seem possible to pass any kind of information into the global function (e.g., store the instances in a global container, but how to pass the index when called?).

std::bind is close to the solution (whose existence is unknown to me), but the critical problem is that the return value of std::bind cannot be cast into a function pointer. Using target<type_of_function_pointer>() of std::function gets me a nil, as the type does not match. When I pretend it to be a function pointer and passed it directly, a segfault happens.

Is this possible after all? Or is my goal just misled? What is the canonical way of dealing with this problem?

Colliot
  • 1,522
  • 3
  • 16
  • 29
  • 1
    Usually this would be done with a `void*`, which the callback function would take as an argument. Your register function would also need to take this pointer and store it, so it could pass it to the callback function when it calls it. – Benjamin Lindley Feb 18 '16 at 18:04
  • It could be very simple: make callback a static member that receives a `void * instance`. Inside it, cast it back to `this` and call private member members with it. To register, just register with `this` and callback. – user3528438 Feb 21 '16 at 01:59
  • @Benjamin Lindley, @user3528438, actually the library is **libuv** and the API is `int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)`. I don't know if I am using it in a wrong way. – Colliot Feb 21 '16 at 07:43
  • Here's a demo adapted from a deleted answer to another question. Maybe it's helpful. http://ideone.com/6sCsdV – user3528438 Feb 29 '16 at 19:44
  • @user3528438, after searching for `void*` and "libuv", I found in the documentation that there exists the desired place to pass in `this`, which is `data` of `uv_stream_t`. – Colliot Mar 01 '16 at 12:24

1 Answers1

2

If you really need a C-style callback, i.e. the one that calls the target callback through a regular function pointer, then the canonical way of dealing with this problem is to implement the actual callback as a "regular" (non-member or static member) function and pass the object pointer to it as an extra parameter

class SomeClass {
  ...
  void member_callback(int param1, double param2) { ... }
  static void static_callback_wrapper(int param1, double param2, void *user_param) {
    return static_cast<SomeClass *>(user_param)->member_callback(param1, params2);
  }
};

Now, if you have an object

SomeClass some_object;

and you want to use that callback with this object, in some external algorithm, you pass a pointer to SomeClass::static_callback_wrapper as the callback, and a pointer to some_object as "void * opaque user-specified data" to that algorithm. Of course, the algorithm has to be implemented with user-specified data in mind - it has to forward the void * pointer you passed from outside back to your callback.

For example, this is the convention pthread_create in pthreads library follows (http://man7.org/linux/man-pages/man3/pthread_create.3.html). The callback you pass to that function is void *(*start_routine) (void *), and the "user-specified data" is passed through the last void *arg parameter. The thread function will be invoked as start_routine(arg) thus forwarding that user-specified pointer back to the callback.

The same convention is used in non-standard qsort_r function (http://linux.die.net/man/3/qsort_r). Bascially, it is a good programming practice to include such "opaque user-specified data" forwarding mechanism into every implementation that employs C-style callbacks.

All this applies it you really need to conform to C-style callback interface. In pure C++ you have other, much more felxible, opportunities. You can represent the callback by a std::function object (as one possibility) and simply bind the implicit this argument of member functions to the object you want to use for the callback, thus turning them into "ordinary" functions.

void some_algorithm(std::function<void ()> callback) {
   ...
   callback();
   ...
   callback();
   ...
}

class SomeClass {
  void foo() {}
};

int main() {
  SomeClass object;
  some_algorithm(std::bind(&SomeClass::foo, &object));
}
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Also in C++, it is often the case that we don't need to use dynamic binding at all, and can just make the function type a template parameter, and this is usually more efficient than function pointers or `std::function` (and can often be inlined) – Benjamin Lindley Feb 18 '16 at 18:16
  • Probably my words are misleading? I used the word adapt, but what I have in my could be wrap, more precisely. I cannot change the C api (e.g. a third-party library, or OS api.). – Colliot Feb 18 '16 at 18:18
  • @Aszune's Heart: Firstly, the first part of my answer specifically applies to working with C-style callback API in C++. A static member function is a "regular" function and can be passed to C API as a callback. However, secondly, as I said above, that C API has to follow the convention with that extra user-specified parameter. That parameter is absolutely required to pass the specific object pointer to the callback. If your C API follows that convention - then everything is easy as I described above. If it doesn't follow that convention, then sorry, there's no credible solution to that issue. – AnT stands with Russia Feb 18 '16 at 18:21
  • @AnT, static functions are among the solutions I have read on stackoverflow and elsewhere. And yes, the API does not expose an argument like `user_param`. – Colliot Feb 18 '16 at 18:28
  • @Aszune's Heart: If the API does not expose an extra argument for forwarding user data to the callback, then unfortunately there's no viable solution at all. For passing extra data to the callback you will be limited to global variables (you already mentioned in your question) with all of their limitations: no thread safety, no reentrability, no way to "manufacture" callback-to-object bindings in run-time quantities (with dynamically created objects). More precisely, some compilers offer non-standard features that help in this situation (aka closures), but this is highly compiler-specific. – AnT stands with Russia Feb 18 '16 at 18:33
  • @Aszune'sHeart This is a little late but I just posted an answer here http://stackoverflow.com/a/35706523/3528438 – user3528438 Feb 29 '16 at 18:43
  • @user3528438: I read your answer and it is not an answer at all. It apparently applies to some strange API's that in addition to callback itself allow you to register some sort of "API payload". Nothing like this is mentioned in this question. All you can register is a function pointer, nothing else. No payload allowed. – AnT stands with Russia Feb 29 '16 at 19:30
  • @AnT I wonder if that's true. Although OP did not state it in the question, but mentioned in the comment he was using `int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)`, so I guess the context can be tied to `uv_stream_t`. – user3528438 Feb 29 '16 at 19:38
  • @user3528438: In that specific case it would be possible, of course, assuming you have full control over allocation of that `uv_stream_t` object (it appears to be the case in libuv, but not so in general). However, I don't see what made you assume that it applies to that other question as well. – AnT stands with Russia Feb 29 '16 at 19:54
  • @AnT I agree. I've deleted that answer. – user3528438 Feb 29 '16 at 19:56