views:

113

answers:

3

How do I setup/register a callback function, in C++, to call a function when there is data to be read from a queue?

Edit 1:

Using Neil's answer for a complete answer (in header file):

#include <vector.h>

class QueueListener {
   public:
       virtual void DataReady(class MyQueue *q) = 0;
       virtual ~QueueListener() {}
};

class MyQueue {
   public:
       void Add (int x) {
          theQueue.push_back(x);
          for (int i = 0; i < theCallBacks.size(); i++) {
             theCallBacks[i]->DataReady(this);
          }
       }

       void Register (QueueListener *ql) {
            theCallBacks.push_back(ql);
       }


   private:
       vector <QueueListener *> theCallBacks;
       vector <int> theQueue;
};



class MyListener : public QueueListener {
   public:
       virtual ~MyListener () {
          printf("MyListener destructor!");
       }
       MyListener(MyQueue *q);
       virtual void DataReady(class MyQueue *p);
};

And the registering:

#include "File1.h"


MyListener::MyListener(MyQueue *q)
{
   q->Register(this);
}

void MyListener::DataReady(class MyQueue *p)
{
   Sleep(500);
}

Then the calls:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    MyQueue *q = new MyQueue();
    MyListener ml(q);

    q->Add(1);

}
+2  A: 

In outline, create a QueueListener base class:

class QueueListener {
   public:
       virtual void DataReady( class MyQueue & q ) = 0;
       virtual ~QueueListener() {}
};

and a queue class (make this queue of integers as example:

class MyQueue {

   public:
      void Add( int x ) {
          theQueue.push_back( x );
          for ( int i = 0; i < theCallBacks.size(); i++ ) {
              theCallBacks[i]->DataReady( * this );
          }
      }

      void Register( QueueListener * ql ) {
          theCallBacks.push_back( ql );
      }

  private:

    vector <QueueListener *> theCallBacks;
    SomeQueueType <int> theQueue;

};

You derive the classes that want to be called back from QueueListener and implement the DataReady function. You then register instances of the derived class with your queue instance.

anon
That feels a bit java-y or at least c++98. I prefer functors and bind myself.
caspin
Thanks Neil - I got it to work. I had to change the *this to a this in the DataReady declaration under MyQueue though.
0A0D
@Caspin First time I've been accused of writing Java-like code. And why didn't you produce a solution using functors and bind?
anon
@Changeling My function used a reference, not a pointer.
anon
I liked the example code given, it was very easy to understand even if someone thinks it's java-y. And you're right about the reference Neil. thanks again saved me some clock cycles
0A0D
+2  A: 

Have a look at Boost.Signals.

Example stolen from tutorial:

struct HelloWorld 
{
  void operator()() const 
  { 
    std::cout << "Hello, World!" << std::endl;
  } 
};

// ...

// Signal with no arguments and a void return value
boost::signal<void ()> sig;

// Connect a HelloWorld slot
HelloWorld hello;
sig.connect(hello);

// Call all of the slots
sig();
Eddy Pronk
+1  A: 

I like the approach that boost.asio uses for callback. In ASIO they are referred to as handlers. Please excuse my c++0x, it is so much faster to write than c++98.

class MyQueue
{
   //...
   Register( const std::function<void()>& callback )
   {
      m_callbacks.push_back(callback);
   }

   Add( const int& i )
   {
      // ...

      for( const auto& callback: m_callbacks )
      {
         callback();
      }
   }

   std::vector<std::function<void()>> m_callbacks;
};

class SomeClass
{
public:
   void SomeQueueIsReady( MyQueue& )
   { /* do something with MyQueue */ }
};

void register_callback()
{
   SomeClass some;
   MyQueue queue;

   // using bind
   queue.Register( std::bind( &SomeClass::SomeQueueIsReady, &some, std::ref(queue) ) );

   // or using a lambda
   queue.Register( [&queue,&some](){ some.SomeQueueIsReady( queue ); } );
}

The key points are the callback is a functor so the user isn't tied to a particular class hierarchy and the callbacks don't take any parameters. If you want parameters passed in, you bind them yourself. The exception is if the callback produces information not available when the callback was registered. An example could be the time when the item was added.

There is nothing stopping you from using this solution in c++98. You cannot use lamdbas, but boost::function and boost::bind are near identical to their c++0x counter parts.

Be aware that you'll have to manage object lifetimes carefully. That is the case with either Neil's or my solution.

caspin