Ou Changkun Ou Changkun - 3 months ago 9
C++ Question

How to warp system call with callback parameter into a C++ class?

I would like to warp a Linux System Call API (clone) into a C++ class.

However, this API required a function pointer, and it's parameter list is fixed, for instance:

typedef int (*callback)(void *);
void system_call(callback f) {
void *t = nullptr;
f(t);
}


Now, My class like:

class Foo {
public:
void start() {
// WRONG: cannot pass non-static member function due to pointer `this`
system_call(this->foo);
}
private:
int foo(void *args) {
f->necessary();
return 0;
}
void necessary() {
std::cout << "call success!!!" << std::endl;
}
};

int main() {
Foo obj;
obj.start();
}


So, the important problems are:


  1. system_call's parameter are fixed and unchangeable.

  2. the method
    start()
    must be non-static.



I was thinking about this, by using a static member:

class Foo {
public:
void start() {
auto func = std::bind(foo, std::placeholders::_1, this);
system_call(func); // WRONG: cannot convert std::_Bind to a function pointer
}
private:
static int foo(void *args, Foo *f) {
f->necessary();
return 0;
}
void necessary() {
std::cout << "call success!!!" << std::endl;
}
};


or this, by using lambda with captures:

class Foo {
public:
void start() {
auto func = [this](void *args) -> int {
this->necessary();
};
system_call(func); // WRONG: cannot convert a lambda with captures to a function pointer
}
private:
void necessary() {
std::cout << "call success!!!" << std::endl;
}
};


They all wrong.

Any solutions to fix this problem?

P.S. I think this is a huge requirements for encapsulation, but here I found some answers are not elegant (they modified the parameter list, not possible for system call):

how to pass a non static-member function as a callback?

Answer

I knew it was an XY problem. I asked specifically which system call in question, and you write that it is clone().

If you review the documentation of clone() you will find that it takes an arg parameter, also:

When the fn(arg) function application returns, the child process termiā€ nates. The integer returned by fn is the exit code for the child process. The child process may also terminate explicitly by calling exit(2) or after receiving a fatal signal.

So, all that's needed is to pass the static callback() function to clone().

Then pass the this pointer for arg.

Then have your callback() invoke the real class method.

void callback(void *arg)
{
    reinterpret_cast<Foo *>(arg)->foo();
}

Not only that, it is trivial to pass arbitrary parameters, using the single pointer:

  1. Define a struct that holds any needed parameters in addition to the pointer to the Foo class whose method is to be invoked.

  2. new an instance of the class, put this into the pointer, and initialize the parameters to pass.

  3. Pass a pointer to this class as the arg parameter to clone().

  4. Have your callback fetch the Foo pointer, and all the parameters from the struct, invoke the class method, passing to this method any needed parameters, then delete the newed parameter struct.

This is a very common design pattern: a function that takes a callback function pointer will also take an additional, second opaque pointer that gets forwarded to the callback-ed function, which it can use to store whatever ancillary data the callback needs. Such as a pointer to an instance of a class whose method should be invoked. Not structuring a callback mechanism this way is actually a poor programming practice.

Comments