Mark wrote:
On Wed, 31 Dec 2003 19:17:50 -0500, Jeff Schwab wrote:
Mark wrote:
Hi Jeff,
I hadn't considered this method, but it looks quite simple. I'm not sure
how it will work with my multithreaded app though because Events are
written onto a message queue and processed in another thread. I'm using an
EventDispatcher class that allows threads to register for certain events,
ie. one thread registers for ALARM events and another for NETWORK_TIMEOUT
events. When an event is generated, the EventDispatcher will send the
event to the appropriate thread(s). My EventDispatcher class doesn't know
about any concrete Events, only the Event base class. This way if I add a
new type of Event down the road I don't have to modify the EventDispatcher.
Given this info will your technique still work? Unfortunately my
experience with templates has been limited to simple std::vector<int> type
stuff.
Thanks Jeff I appreciate it!
Mark
Thanks for explaining; no, the technique I described won't work in this
case. Would it be possible to make "process" a virtual method of the
Event base class? This is a common way of implementing the sort of
polymorphism you seem to need. E.g.:
struct Event
{
virtual void process( ) =0;
};
struct Alarm: Event
{
void process( ); // Overrides Event::process( ).
private:
// Data relevant to Alarm event.
};
struct NetworkTimeout: Event
{
void process( ); // Overrides Event::process( ).
};
// Other event types...
Looks like the Command pattern. This would be a nice solution, except
that the Event and the processing associated with the event can vary
independantly. For example I have one thread that is managing a socket
connection and another that is managing a log. Both register to receive
the ALARM event but the processing done is different in each case (ie.
send a message over the socket, write an entry into the log). Maybe I'm
going about the solution the wrong way. I considered having a separate
Command pattern type class that is passed into the Event when registered,
but that would require writing *alot* of small classes which doesn't
appeal to me. Maybe I'll come with something better tomorrow, I've been
at it a while today...
Aha! Sounds like you need call-backs: just one more level of
indirection.
For each type of thread, define a struct to provide handlers for all the
relevant types of Event. Provide a virtual method in the Event class;
inside the virtual method, the exact type of the Event can be known, and
the event can call back the appropriate routine from the Thread's
handler struct.
I've coded an approach here that assumes the Event module knows nothing
about what types of Thread may exist. The approach relies on two levels
of run-time indirection. If you're willing to couple the Events more
strongly to the Thread types, you can get away with only one level of
run-time indirection by overloading the Event::call method for each type
of Thread handler; that way, the Handler methods don't need to be
virtual, and in fact the base Handler class need not exist.
Please let me know if I've explained this poorly, or if there is any
problem with the code that makes this approach inappropriate.
-Jeff
namespace Events
{
struct Alarm;
struct Network_Timeout;
struct Handler
{
virtual void handle( Alarm& ) =0;
virtual void handle( Network_Timeout& ) =0;
};
struct Event
{
virtual void call( Handler& ) =0;
};
struct Alarm: Event
{
void call( Handler& h ) { h.handle( *this ); }
private:
// Alarm-specific data.
};
struct Network_Timeout: Event
{
void call( Handler& h ) { h.handle( *this ); }
};
}
#include <iostream>
namespace Threads
{
struct Socket_Connection
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& )
{
std::cout <<
"A Socket_Connection thread is handling an "
"Alarm event.\n";
}
void handle( Events::Network_Timeout& )
{
std::cout <<
"A Socket_Connection thread is handling a "
"Network_Timeout event.\n";
}
};
void process( Events::Event& event )
{
Handler handler;
event.call( handler );
}
};
struct Log_Manager
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& event )
{
std::cout <<
"A Log_Manager thread is handling an "
"Alarm event.\n";
}
void handle( Events::Network_Timeout& event )
{
std::cout <<
"A Log_Manager thread is handling a "
"Network_Timeout event.\n";
}
};
void process( Events::Event& event )
{
Handler handler;
event.call( handler );
}
};
}
int main( )
{
Threads::Socket_Connection socket_connection;
Threads::Log_Manager log_manager;
Events::Alarm alarm;
Events::Network_Timeout network_timeout;
Events::Event* events[ ] = { &alarm, &network_timeout };
for( Events::Event** p = events; p < events + 2; ++p )
{
socket_connection.process( **p );
log_manager.process( **p );
}
}