How can I pass a class member function as a callback?

前端 未结 12 1748
忘掉有多难
忘掉有多难 2020-11-22 04:58

I\'m using an API that requires me to pass a function pointer as a callback. I\'m trying to use this API from my class but I\'m getting compilation errors.

Here is

相关标签:
12条回答
  • 2020-11-22 05:34

    The type of pointer to non-static member function is different from pointer to ordinary function.
    Type is void(*)(int) if it’s an ordinary or static member function.
    Type is void(CLoggersInfra::*)(int) if it’s a non-static member function.
    So you cannot pass a pointer to a non-static member function if it is expecting an ordinary function pointer.

    Furthermore, a non-static member function has an implicit/hidden parameter to the object. The this pointer is implicitly passed as an argument to the member function call. So the member functions can be invoked only by providing an object.

    If the API Init cannot be changed, a wrapper function (ordinary function or a class static member function) that invokes the member can be used. In the worst case, the object would be a global for the wrapper function to access.

    CLoggersInfra* pLoggerInfra;
    
    RedundencyManagerCallBackWrapper(int val)
    {
        pLoggerInfra->RedundencyManagerCallBack(val);
    }
    
    m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);
    

    If the API Init can be changed, there are many alternatives - Object non-static member function pointer, Function Object, std::function or Interface Function.

    See the post on callbacks for the different variations with C++ working examples.

    0 讨论(0)
  • 2020-11-22 05:35

    This answer is a reply to a comment above and does not work with VisualStudio 2008 but should be preferred with more recent compilers.


    Meanwhile you don't have to use a void pointer anymore and there is also no need for boost since std::bind and std::function are available. One advantage (in comparison to void pointers) is type safety since the return type and the arguments are explicitly stated using std::function:

    // std::function<return_type(list of argument_type(s))>
    void Init(std::function<void(void)> f);
    

    Then you can create the function pointer with std::bind and pass it to Init:

    auto cLoggersInfraInstance = CLoggersInfra();
    auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
    Init(callback);
    

    Complete example for using std::bind with member, static members and non member functions:

    #include <functional>
    #include <iostream>
    #include <string>
    
    class RedundencyManager // incl. Typo ;-)
    {
    public:
        // std::function<return_type(list of argument_type(s))>
        std::string Init(std::function<std::string(void)> f) 
        {
            return f();
        }
    };
    
    class CLoggersInfra
    {
    private:
        std::string member = "Hello from non static member callback!";
    
    public:
        static std::string RedundencyManagerCallBack()
        {
            return "Hello from static member callback!";
        }
    
        std::string NonStaticRedundencyManagerCallBack()
        {
            return member;
        }
    };
    
    std::string NonMemberCallBack()
    {
        return "Hello from non member function!";
    }
    
    int main()
    {
        auto instance = RedundencyManager();
    
        auto callback1 = std::bind(&NonMemberCallBack);
        std::cout << instance.Init(callback1) << "\n";
    
        // Similar to non member function.
        auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
        std::cout << instance.Init(callback2) << "\n";
    
        // Class instance is passed to std::bind as second argument.
        // (heed that I call the constructor of CLoggersInfra)
        auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                                   CLoggersInfra()); 
        std::cout << instance.Init(callback3) << "\n";
    }
    

    Possible output:

    Hello from non member function!
    Hello from static member callback!
    Hello from non static member callback!
    

    Furthermore using std::placeholders you can dynamically pass arguments to the callback (e.g. this enables the usage of return f("MyString"); in Init if f has a string parameter).

    0 讨论(0)
  • 2020-11-22 05:35

    This question and answer from the C++ FAQ Lite covers your question and the considerations involved in the answer quite nicely I think. Short snippet from the web page I linked:

    Don’t.

    Because a member function is meaningless without an object to invoke it on, you can’t do this directly (if The X Window System was rewritten in C++, it would probably pass references to objects around, not just pointers to functions; naturally the objects would embody the required function and probably a whole lot more).

    0 讨论(0)
  • 2020-11-22 05:38

    I can see that the init has the following override:

    Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)
    

    where CALLBACK_FUNC_EX is

    typedef void (*CALLBACK_FUNC_EX)(int, void *);
    
    0 讨论(0)
  • 2020-11-22 05:40

    This is a simple question but the answer is surprisingly complex. The short answer is you can do what you're trying to do with std::bind1st or boost::bind. The longer answer is below.

    The compiler is correct to suggest you use &CLoggersInfra::RedundencyManagerCallBack. First, if RedundencyManagerCallBack is a member function, the function itself doesn't belong to any particular instance of the class CLoggersInfra. It belongs to the class itself. If you've ever called a static class function before, you may have noticed you use the same SomeClass::SomeMemberFunction syntax. Since the function itself is 'static' in the sense that it belongs to the class rather than a particular instance, you use the same syntax. The '&' is necessary because technically speaking you don't pass functions directly -- functions are not real objects in C++. Instead you're technically passing the memory address for the function, that is, a pointer to where the function's instructions begin in memory. The consequence is the same though, you're effectively 'passing a function' as a parameter.

    But that's only half the problem in this instance. As I said, RedundencyManagerCallBack the function doesn't 'belong' to any particular instance. But it sounds like you want to pass it as a callback with a particular instance in mind. To understand how to do this you need to understand what member functions really are: regular not-defined-in-any-class functions with an extra hidden parameter.

    For example:

    class A {
    public:
        A() : data(0) {}
        void foo(int addToData) { this->data += addToData; }
    
        int data;
    };
    
    ...
    
    A an_a_object;
    an_a_object.foo(5);
    A::foo(&an_a_object, 5); // This is the same as the line above!
    std::cout << an_a_object.data; // Prints 10!
    

    How many parameters does A::foo take? Normally we would say 1. But under the hood, foo really takes 2. Looking at A::foo's definition, it needs a specific instance of A in order for the 'this' pointer to be meaningful (the compiler needs to know what 'this' is). The way you usually specify what you want 'this' to be is through the syntax MyObject.MyMemberFunction(). But this is just syntactic sugar for passing the address of MyObject as the first parameter to MyMemberFunction. Similarly, when we declare member functions inside class definitions we don't put 'this' in the parameter list, but this is just a gift from the language designers to save typing. Instead you have to specify that a member function is static to opt out of it automatically getting the extra 'this' parameter. If the C++ compiler translated the above example to C code (the original C++ compiler actually worked that way), it would probably write something like this:

    struct A {
        int data;
    };
    
    void a_init(A* to_init)
    {
        to_init->data = 0;
    }
    
    void a_foo(A* this, int addToData)
    { 
        this->data += addToData;
    }
    
    ...
    
    A an_a_object;
    a_init(0); // Before constructor call was implicit
    a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);
    

    Returning to your example, there is now an obvious problem. 'Init' wants a pointer to a function that takes one parameter. But &CLoggersInfra::RedundencyManagerCallBack is a pointer to a function that takes two parameters, it's normal parameter and the secret 'this' parameter. That's why you're still getting a compiler error (as a side note: If you've ever used Python, this kind of confusion is why a 'self' parameter is required for all member functions).

    The verbose way to handle this is to create a special object that holds a pointer to the instance you want and has a member function called something like 'run' or 'execute' (or overloads the '()' operator) that takes the parameters for the member function, and simply calls the member function with those parameters on the stored instance. But this would require you to change 'Init' to take your special object rather than a raw function pointer, and it sounds like Init is someone else's code. And making a special class for every time this problem comes up will lead to code bloat.

    So now, finally, the good solution, boost::bind and boost::function, the documentation for each you can find here:

    boost::bind docs, boost::function docs

    boost::bind will let you take a function, and a parameter to that function, and make a new function where that parameter is 'locked' in place. So if I have a function that adds two integers, I can use boost::bind to make a new function where one of the parameters is locked to say 5. This new function will only take one integer parameter, and will always add 5 specifically to it. Using this technique, you can 'lock in' the hidden 'this' parameter to be a particular class instance, and generate a new function that only takes one parameter, just like you want (note that the hidden parameter is always the first parameter, and the normal parameters come in order after it). Look at the boost::bind docs for examples, they even specifically discuss using it for member functions. Technically there is a standard function called [std::bind1st][3] that you could use as well, but boost::bind is more general.

    Of course, there's just one more catch. boost::bind will make a nice boost::function for you, but this is still technically not a raw function pointer like Init probably wants. Thankfully, boost provides a way to convert boost::function's to raw pointers, as documented on StackOverflow here. How it implements this is beyond the scope of this answer, though it's interesting too.

    Don't worry if this seems ludicrously hard -- your question intersects several of C++'s darker corners, and boost::bind is incredibly useful once you learn it.

    C++11 update: Instead of boost::bind you can now use a lambda function that captures 'this'. This is basically having the compiler generate the same thing for you.

    0 讨论(0)
  • 2020-11-22 05:41

    Is m_cRedundencyManager able to use member functions? Most callbacks are set up to use regular functions or static member functions. Take a look at this page at C++ FAQ Lite for more information.

    Update: The function declaration you provided shows that m_cRedundencyManager is expecting a function of the form: void yourCallbackFunction(int, void *). Member functions are therefore unacceptable as callbacks in this case. A static member function may work, but if that is unacceptable in your case, the following code would also work. Note that it uses an evil cast from void *.

    
    // in your CLoggersInfra constructor:
    m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);
    
    
    // in your CLoggersInfra header:
    void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);
    
    
    // in your CLoggersInfra source file:
    void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
    {
        ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
    }
    
    0 讨论(0)
提交回复
热议问题