Events in C++

W

Wim

Hi,

I'm looking for some help on how to best implement an observer pattern in
C++. I have an object that several objects will register with, and on
each event, all the objects are notified.
Any hints about existing libraries/code I could use for this are also
welcome!
Here is a simple example of the code I'm using now:

class ExampleListener
{
public:
virtual ~ExampleListener() { }
virtual void onReceive(ExampleData*) = 0;
};

class ExampleProcessor : public ExampleListener
{
private:
vector<ExampleListener*> todo;
public:
virtual void onData(ExampleData* data)
{
// todo.push_back(data); would cause segfaults later on
todo.push_back( new ExampleData(data) );
}
//...
};

class ExampleGenerator
{
private:
vector<ExampleListener*> listeners;
void fire(ExampleData* d)
{
for (it = listeners.begin(); it != listeners.end(); it++)
(*it)->onData(d);
}
public:
void addListener(ExampleListener* l)
{
listeners.push_back(l);
}
void run()
{
//...
fire(x); delete x;
//..
fire(y); delete y;
//..
}
};

int main()
{
ExampleProcessor a, b;
ExampleGenerator g;

g.addListener(a);
g.addListener(b);

g.run();
}

In my situation, ExampleProcessor typically either stores the data, or
does nothing with it. I don't like the fact that it has to do the copy
itself, it's just too easy to forget!
I though about passing an auto_ptr like this: virtual void onData
(auto_ptr<ExampleData> data) But if I do that, I have to copy the
data for each listener, while typically, only 1 listener will need to
store it. Perhaps some smarter version of auto_ptr is handy here? Any
comments and hints on how to do this best are more than welcome!


greetings,
Wim
 
M

Marcel Müller

Hi,
I'm looking for some help on how to best implement an observer pattern in
C++. I have an object that several objects will register with, and on
each event, all the objects are notified.

this is an interesting topic, but since events are usually used in
multi-threaded environments you only touched the surface so far.

Here is a simple example of the code I'm using now:
[...]

I would recommend to encapsulate the list of listener in the generator
too, since this pattern is likely to be repeated.

In my situation, ExampleProcessor typically either stores the data, or
does nothing with it. I don't like the fact that it has to do the copy
itself, it's just too easy to forget!
I though about passing an auto_ptr like this: virtual void onData
(auto_ptr<ExampleData> data) But if I do that, I have to copy the
data for each listener, while typically, only 1 listener will need to
store it. Perhaps some smarter version of auto_ptr is handy here? Any
comments and hints on how to do this best are more than welcome!

Don't use auto_ptrs here for the reasons you already mentioned. Raising
an event is typically a synchronous from the generators point of view.
So it is straight forward that the generator holds the ownership of the
parameters wherever hi like (local, static, dynamic storage).


First of all you should check your needs.

- Should your observer be thread-safe? - Most likely yes.
generators and listeners are usually in a different thread. And
addListener is usually called from the listener's thread, while fire is
called from the generator's thread.

- Do you want a strong or a weak reference from the generator to the
listeners? In other words: should a listener automatically deregister
from the generator when it is destroyed. I recommend yes.

- Are there dependent objects who's lifetime should be influenced by the
registration?

- Is it allowed that a listener's lifetime exceeds the lifetime of the
generator where it is registered?

- Should one listener instance be usable for more than one generator at
the same time?
I recommend no. It just simplifies things.

- What should happen when an event is fired while a listener is going to
deregister?
1. wait for the other to complete.
This is likely to cause deadlocks. At least if the underlying mutex is
not recursive, because it is a common practice to deregister from a
generator within the callback code. Furthermore the listeners must not
acquire any other mutex within the receive function. This ist difficult
to achieve.
2. proceed.
This has the risk that the listener's receive method still may depend on
objects whose lifetime ends immediately after the deregistration.
3. some compromise between 1 and 2.

- Is it acceptable to have one mutex instance for each generator
instance? - I recommend not.
If not you need some global critical mutex object which must be
initialized before any listener is registered. This may be difficult if
the registration is done from the constructor of static objects.

- Do you need a second parameter to the receive function that comes from
the listener's instance?
I recommend yes. While this is strictly speaking not required because
you always can derive from the listener, it is more convenient because
you do not always have to subclass for each use pattern. A template
subclass of a non-template base is a good solution.


Let me know if you want example coding. I have implementations for this
purpose, but they are a bit too long to be copied and fully explained
here without demand.


Marcel
 
W

Wim

Thanks for your reply!

this is an interesting topic, but since events are usually used in
multi-threaded environments you only touched the surface so far.

I've actually been able to avoid multi-threading in the specific case I'm
working on, which makes it all a lot easier!
Also, I currently have only 1 generator which lives forever, and that
also simplifies things. So I'm currently not in the most challenging or
interesting situation.
I've been using events in all sorts of situations, and will undoubtedly
use them even more in the future, so this is very interesting topic
indeed. I find there are quite a lot of scenarios where they are useful.

Don't use auto_ptrs here for the reasons you already mentioned. Raising
an event is typically a synchronous from the generators point of view.
So it is straight forward that the generator holds the ownership of the
parameters wherever hi like (local, static, dynamic storage).

I agree that it should be straight forward. But in what I'm currently
writing, the generator is under my control, while the Listeners will be
written by someone else. And I really want to avoid that other person
screwing things up by not copying.

Currently, I just have an attention-drawing comment in the interface,
explaining the need to copy.

I've also been thinking that passing a const reference instead of a
pointer is better:
onData(const ExampleData& data) instead of onData(ExampleData* data)

But I fear that in that case they'll just do:
todo.push_back( &data );

So I guess my question isn't really that much about the event themselves
(as I use a simplified case), but "How I can I make it as idiot proof as
possible?" ;-)


First of all you should check your needs.
...
- Do you want a strong or a weak reference from the generator to the
listeners? In other words: should a listener automatically deregister
from the generator when it is destroyed. I recommend yes.

Most other points are very useful in other cases, but not in my specific
case. This one however, I almost forgot, so thanks a lot!

- Do you need a second parameter to the receive function that comes from
the listener's instance?
I recommend yes. While this is strictly speaking not required because
you always can derive from the listener, it is more convenient because
you do not always have to subclass for each use pattern. A template
subclass of a non-template base is a good solution.

What exactly do you mean? I'm not sure I understand...



greetings,
Wim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,602
Members
45,182
Latest member
BettinaPol

Latest Threads

Top