|
|
|
#1 |
|
Messages: n/a
Hébergeur: |
Hi,
I'm looking for some 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 |
|
|
|
#2 |
|
Messages: n/a
Hébergeur: |
Hi,
Wim wrote: > I'm looking for some 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 |
|
|
|
#3 |
|
Messages: n/a
Hébergeur: |
> I'm looking for some 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! Two good libraries for this are: - libsigc++ (http://libsigc.sourceforge.net/) - Boost.Signals (http://www.boost.org/doc/libs/1_35_0...l/signals.html) |
|
|
|
#4 |
|
Messages: n/a
Hébergeur: |
Thanks for your reply!
On Mon, 07 Apr 2008 13:21:56 +0200, Marcel Müller wrote: >> ... > 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. >> 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). 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 |
|
![]() |
| Outils de la discussion | |
|
|