9

I have created a Timer class that must call a callback method when the timer has expired. Currently I have it working with normal function pointers (they are declared as void (*)(void), when the Elapsed event happens the function pointer is called.

Is possible to do the same thing with a member function that has also the signature void (AnyClass::*)(void)?

Thanks mates.

EDIT: This code has to work on Windows and also on a real-time OS (VxWorks) so not using external libraries would be great.

EDIT2: Just to be sure, what I need is to have a Timer class that take an argument at the Constructor of tipe "AnyClass.AnyMethod" without arguments and returning void. I have to store this argument and latter in a point of the code just execute the method pointed by this variable. Hope is clear.

Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • Are you sure the function pointer isn't declared as `void(*)(void*)`. I would be extremely shocked if it did not accept a `void*` as a paramter. – deft_code Jan 27 '10 at 16:35

5 Answers5

9

Dependencies, dependencies... yeah, sure boost is nice, so is mem_fn, but you don't need them. However, the syntax of calling member functions is evil, so a little template magic helps:

   class Callback
   {
   public:
      void operator()() { call(); };
      virtual void call() = 0;
   };

   class BasicCallback : public Callback
   {
      // pointer to member function
      void (*function)(void);
   public:
      BasicCallback(void(*_function)(void))
          : function( _function ) { };
      virtual void call()
      { 
          (*function)();
      };
   };   

   template <class AnyClass> 
   class ClassCallback : public Callback
   {
      // pointer to member function
      void (AnyClass::*function)(void);
      // pointer to object
      AnyClass* object;        
   public:
      ClassCallback(AnyClass* _object, void(AnyClass::*_function)(void))
          : object( _object ), function( _function ) { };
      virtual void call()
      { 
          (*object.*function)();
      };
   };

Now you can just use Callback as a callback storing mechanism so:

void set_callback( Callback* callback );
set_callback( new ClassCallback<MyClass>( my_class, &MyClass::timer ) );

And

Callback* callback = new ClassCallback<MyClass>( my_class, &MyClass::timer ) );

(*callback)();
// or...
callback->call();
Kornel Kisielewicz
  • 55,802
  • 15
  • 111
  • 149
  • Ok, take in mind that I do not understand almost nothing here. C++ is not what I use to work with, I just need to do some changes to a running project so I expect to avoid understanding all of this magic. How can I execute the callback? If I just put callback(); i get a term does not evaluate to a function taking 0 arguments. Thanks in advance. – Ignacio Soler Garcia Jan 27 '10 at 16:21
  • And the class BasicCallback looks like is not used anywhere. Maybe there is a mistake there? – Ignacio Soler Garcia Jan 27 '10 at 16:31
  • @SoMoS, BasicCallback allows you to use the same interface with your old callbacks -- it doesn't take a class. You did get your error because you tried to "execute" a pointer, `(*callback)()` would be the proper call. I modified my code a little and added a explicit `call` method, maybe that will be clearer. – Kornel Kisielewicz Jan 27 '10 at 16:35
  • Thanks, (*callback)() is enough for me. You help has saved me a ton of hours. Thanks a lot. – Ignacio Soler Garcia Jan 27 '10 at 16:45
  • Oh my god. I was trying to create my own callback for hell lot of time. Thank you for thins, it finally works – Jan Glaser May 23 '16 at 14:46
4

The best solution I have used for that same purpose was boost::signal or boost::function libraries (depending on whether you want a single callback or many of them), and boost::bind to actually register the callbacks.

class X {
public:
   void callback() {}
   void with_parameter( std::string const & x ) {}
};
int main()
{
   X x1, x2;
   boost::function< void () > callback1;

   callback1 = boost::bind( &X::callback, &x1 );
   callback1(); // will call x1.callback()

   boost::signal< void () > multiple_callbacks;
   multiple_callbacks.connect( boost::bind( &X::callback, &x1 ) );
   multiple_callbacks.connect( boost::bind( &X::callback, &x2 ) );
   // even inject parameters:
   multiple_callbacks.connect( boost::bind( &X::with_parameter, &x1, "Hi" ) );

   multiple_callbacks(); // will call x1.callback(), x2.callback and x1.with_parameter("Hi") in turn
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

Maybe the standard mem_fun is already good enough for what you want. It's part of STL.

Maurits Rijk
  • 9,789
  • 2
  • 36
  • 53
  • I proposed boost::function as I was assuming that the Timer would be a non-templated class that had to keep the callback for later use. Since mem_fun is templated, either you teplate the timer class on the class of the callback, or else you will have to wrap the mem_fun in a non-templated class, and here is where you end up implementing boost::function yourself... – David Rodríguez - dribeas Jan 27 '10 at 15:57
1

boost::function looks like a perfect fit here.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
0

I'm assuming an interface like this:

void Timer::register_callback( void(*callback)(void*user_data), void* user_data );

template<typename AnyClass, (AnyClass::*Func_Value)(void)>
void wrap_method_callback( void* class_pointer )
{
   AnyClass*const self = reinterpret_cast<AnyClass*>(class_pointer);
   (self->*Func_Value)();
}

class A
{
public:
   void callback()
   { std::cout << m_i << std::endl; }
   int m_i;
};

int main ()
{
   Timer t;
   A a = { 10 };
   t.register_callback( &wrap_method_callback<A,&A::callback>, &a );
}

I think a better solution would be to upgrade call you callback to either use boost::function or a homegrown version (like Kornel's answer). However this require real C++ developers to get involved, otherwise you are very likely to introduce bugs.

The advantage of my solution is that it is just one template function. Not a whole lot can go wrong. One disadvantage of my solution is it may slice your class with cast to void* and back. Be careful that only AnyClass* pointers are passes as void* to the callback registration.

deft_code
  • 57,255
  • 29
  • 141
  • 224