I have one Animation class. I need to have some observers for Play
, Pause
and Stop
events in the Animation. I found 2 solutions for this problem, but I dont't know what to chose.
Use boost::signals or something similar and register callbacks for every event
Make a simple interface with 3 pure virtual functions(OnPlay()
, OnPause()
, OnStop()
) and pass to the Animation class objects that implement this interface.
There are advantages and disadvantages for every method. I'll try to enumerate the ones that I found so far:
Advantages for 1.
Disadvantages for 1.
Advantages for 2.
Disadvantages for 2.
Can you please advise me what to use? From your experience what is better for this problem - the freedom from first aproach or clear and easy to understand code from the second one? Can you please give me other advantages/disadvantages for both methods or other solution?
First of all, it would be useful to know if the "binding" is known at compile-time or not. If so, I would suggest you to look into policy classes.
Apart from that, I would go for a mix of the two solutions, i.e. use the interface approach and implement one interface which acts as a relayer for signals/free-functions. This way you can have default behaviours, you can add custom objects implementing the whole interface and have, basically, the advantages of the two approaches as well as much flexibility.
Here is a basic example of the proposed approach, I hope it is of help.
#include <functional>
using namespace std;
template <class ObserverPolicy>
class Animation : public ObserverPolicy{
};
class MonolithicObserver{
public:
void play(){
state = playing;
}
void pause(){
if(playing == state)
state = stopped;
}
void stop(){
state = stopped;
}
private:
enum {playing, paused, stopped} state;
};
struct doNothing{
static void play(){}
static void pause(){}
static void stop(){}
};
struct throwException{
class noPlay{};
class noPause{};
class noStop{};
static void play(){
throw noPlay();
}
static void pause(){
throw noPause();
}
static void stop(){
throw noStop();
}
};
template <class DefaultPolicy = doNothing>
class FreeFunctionObserver{
public:
void play(){
if(playHandle)
playHandle();
else
DefaultPolicy::play();
}
void pause(){
if(pauseHandle)
pauseHandle();
else
DefaultPolicy::pause();
}
void stop(){
if(stopHandle)
stopHandle();
else
DefaultPolicy::stop();
}
void setPlayHandle(std::function<void(void)> p){
playHandle = p;
}
void setPauseHandle(std::function<void(void)> p){
pauseHandle = p;
}
void setStopHandle(std::function<void(void)> p){
stopHandle = p;
}
private:
std::function<void(void)> playHandle;
std::function<void(void)> pauseHandle;
std::function<void(void)> stopHandle;
};
void play(){}
void pause(){}
void stop(){}
int main(){
Animation<FreeFunctionObserver<> > affo;
affo.setPlayHandle(play);
affo.setPauseHandle(pause);
affo.setStopHandle(stop);
affo.play();
affo.pause();
affo.stop();
Animation<FreeFunctionObserver<throwException> > affot;
try{
affot.play();
}
catch(throwException::noPlay&){}
Animation<MonolithicObserver> amo;
amo.play();
amo.pause();
amo.stop();
}
which you can try here. This example, in particular, uses a policy class (hence no interface is "formally" defined, and you can "enrich" the interface, as done with setPlayHandle). However, you can do something similar with run-time binding too.
For all but the simplest of toy examples, Boost.Signals2 would be the superior solution in my opinion. It is well-designed, well-tested and well-documented. Reinventing the wheel is nice for homework type of exercises, but not for production code. E.g. making your own observer thread-safe is non-trivial to get right and efficient.
Specifically discussing your listed disadvantages
boost::bind
syntax (which is not really complicated for most usage anyway)TL;DR: familiarize yourself with Boost.Signals2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With