Search code examples
c++templatestypescallbackdecouple

C++ Callback -- How to decouple the call back type


For example, in the following pseudo code, Class B need to call A::Action() through B::m_cb member.

The objective is, how to make a general, non-template Callback class, so "B" does not have to be a template, and the "CallBack" can hold any kind of function signature.

I ever use such code before, but now I can not find that implementation. All I remember is:
- the "CallBack" itself is not a template, but it contains member template
- the helper function template make_callback will instantiate CallBack object

Can anyone give a poiinter?

Class A
{
public:
   void Action(){//...};
};

class CallBack
{
   //...
   // CallBack it self it is a NOT a template
   // It can wrap member template though
};

class B
{
public:
   void SetCallback(CallBack to){
      m_cb = to;
   }
   void do_something()
   {
      //...
      m_cb.Execute();
      //...
   }

private:
   CallBack m_cb;

};

int main()
{
   A obj1;
   CallBack cb = make_callback(&obj1, &A::Action);
   B obj2;
   obj2.SetCallback(cb);
   //....
   obj2.do_something();
}

Here is the sample code I got from this same website. I tried to improved it a little bit, so it can tolerate arbitrary call back function's return type. But it still can not handle arbitrary number of arguments, like in line 18. Also, , T is the pointer to member function, which should be depend on C. I don't know how to enforce this.

#include <iostream>
#include <memory>

// INTERNAL CLASSES

class CallbackSpecBase
{
  public:
    virtual ~CallbackSpecBase() {}
    virtual void operator()(...) const = 0;
};

template<class C, class T>
class CallbackSpec : public CallbackSpecBase
{
  public:
    CallbackSpec(C& o, T m) : obj(o), method(m) {}
/*line 18*/    void operator()(...) const { (&obj->*method)(); } // how to pass "..." into method(...)

  private:
    C& obj;
    T method;
};

// PUBLIC API

class Callback
{
  public:
    Callback() {}

    void operator()() { (*spec)(); }

    template<class C, class T>
      void set(C& o, T m) { spec.reset(new CallbackSpec<C, T>(o, m)); }

  private:
    std::auto_ptr<CallbackSpecBase> spec;
};

// TEST CODE

class Test
{
  public:
    void foo() { std::cout << "Working" << std::endl; }
    void bar() { std::cout << "Like a charm" << std::endl; }
};

int main()
{
  Test t;
  Callback c;
  c.set(t, &Test::foo);
  c();
  c.set(t, &Test::bar);
  c();
}

Solution

  • This doesn't really answer your question, because a callback has to be a template, unless you want to pass a parameter through void* (which is totally crazy idea in my opinion).

    I asked a similar question : What is wrong with this variadic templates example?

    One of answers gave a complete solution :

    #include <memory>
    
    template< typename R, typename ... Args >
    class CallbackBase
    {
    public:
        typedef std::shared_ptr< CallbackBase< R, Args... > >
                CallbackPtr;
    
        virtual ~CallbackBase()
        {
        }
        virtual R Call(  Args ... args) = 0;
    };
    
    template< typename R, typename ... Args >
    class FunctionCallback : public CallbackBase< R, Args... >
    {
    public:
        typedef R (*funccb)(Args...);
    
        FunctionCallback( funccb  cb_ ) : 
            CallbackBase< R, Args... >(),
            cb( cb_ )
        {
        }
        virtual ~FunctionCallback()
        {
        }
        virtual R Call(Args... args)
        {
          return cb( args... );
        }
    private:
      funccb cb;
    };
    
    template < typename R, typename ...Args >
    typename CallbackBase< R, Args... >::CallbackPtr
        MakeCallback( R (*cb)(Args...)  )
    {
        typename CallbackBase< R, Args... >::CallbackPtr
            p( new FunctionCallback< R, Args... >( cb )
    );
        return p;
    }
    
    bool Foo_1args( const int & t)
    {
        return true;
    }
    int main()
    {
        auto cbObj = MakeCallback( & Foo_1args );
    }
    

    Hope it helps. If you hate templates, you can always use a typedef.