[added: comp.lang.c++
here is link to Ulrich Eckhardt's full post because I snipped some of it:
http://groups.google.com/group/comp....190e3b9ac81a69
]
"Ulrich Eckhardt" <do******@knuut .dewrote in message
news:6n******** ****@mid.uni-berlin.de...
Chris M. Thomasson wrote:
[C++ thread baseclass with virtual run() function]
Just one thing technical about the code: your use reinterpret_cas t in a
place that actually calls for a static_cast. A static_cast is the right
tool to undo the implicit conversion from T* to void*.
>I personally like this technique better than Boost. I find it more
straight forward and perhaps more object oriented, the RAII nature of the
`active' helper class does not hurt either. Also, I really do think its
more "efficient" than Boost in the way it creates threads because it does
not copy anything...
There are two things that strike me here:
1. You mention "object oriented" as if that was a goal, but it isn't.
Rather, it is a means to achieve something, and the question is always
valid whether its use is justified. Java's decision to force an OO design
on you and then inviting other paradigms back in through the backdoor is
the prime example for misunderstood OO. Wrapping a thread into a class the
way you do it is another IMHO, ill explain below.
2. What exactly is the problem with the copying? I mean you're starting a
thread, which isn't actually a cheap operation either. Further, if you
want, you can optimise that by using transfer of ownership (auto_ptr) or
shared ownership (shared_ptr) in case you need. Boost doesn't make it
necessary to copy anything either (except under the hood it does some
dynamic allocation), but also allows you to chose if you want. However,
copying and thus avoiding shared data is the safer default, because any
shared data access requires care.
[...]
If find it unfortunate to be forced to use an smart pointers and dynamically
created objects just to be able to pass common shared data to a thread. I am
still not convinced that treating a thread as an object is a bad thing...
For instance...
How would I be able to create the following fully compliable program (please
refer to the section of code under the "Simple Example" heading, the rest is
simple impl detail for pthread abstraction) using Boost threads. The easy
way to find the example program is to go to the end of the entire program,
and start moving up until you hit the "Simple Example" comment... Here it
is:
_______________ _______________ _______________ _______________ _________
/* Simple Thread Object
_______________ _______________ _______________ _______________ __*/
#include <pthread.h>
extern "C" void* thread_entry(vo id*);
class thread_base {
pthread_t m_tid;
friend void* thread_entry(vo id*);
virtual void on_active() = 0;
public:
virtual ~thread_base() = 0;
void active_run() {
pthread_create( &m_tid, NULL, thread_entry, this);
}
void active_join() {
pthread_join(m_ tid, NULL);
}
};
thread_base::~t hread_base() {}
void* thread_entry(vo id* state) {
reinterpret_cas t<thread_base*> (state)->on_active();
return 0;
}
template<typena me T>
struct active : public T {
struct guard {
T& m_object;
guard(T& object) : m_object(object ) {
m_object.active _run();
}
~guard() {
m_object.active _join();
}
};
active() : T() {
this->active_run() ;
}
~active() {
this->active_join( );
}
template<typena me T_p1>
active(T_p1 p1) : T(p1) {
this->active_run() ;
}
template<typena me T_p1, typename T_p2>
active(T_p1 p1, T_p2 p2) : T(p1, p2) {
this->active_run() ;
}
template<typena me T_p1, typename T_p2, typename T_p3>
active(T_p1 p1, T_p2 p2, T_p3 p3) : T(p1, p2, p3) {
this->active_run() ;
}
// [and on and on for more params...]
};
/* Simple Monitor
_______________ _______________ _______________ _______________ __*/
class monitor {
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
public:
monitor() {
pthread_mutex_i nit(&m_mutex, NULL);
pthread_cond_in it(&m_cond, NULL);
}
~monitor() {
pthread_cond_de stroy(&m_cond);
pthread_mutex_d estroy(&m_mutex );
}
struct lock_guard {
monitor& m_monitor;
lock_guard(moni tor& monitor_) : m_monitor(monit or_) {
m_monitor.lock( );
}
~lock_guard() {
m_monitor.unloc k();
}
};
struct signal_guard {
monitor& m_monitor;
bool const m_broadcast;
signal_guard(mo nitor& monitor_, bool broadcast = true)
: m_monitor(monit or_), m_broadcast(bro adcast) {
}
~signal_guard() {
if (m_broadcast) {
m_monitor.broad cast();
} else {
m_monitor.signa l();
}
}
};
void lock() {
pthread_mutex_l ock(&m_mutex);
}
void unlock() {
pthread_mutex_u nlock(&m_mutex) ;
}
void wait() {
pthread_cond_wa it(&m_cond, &m_mutex);
}
void signal() {
pthread_cond_si gnal(&m_cond);
}
void broadcast() {
pthread_cond_br oadcast(&m_cond );
}
};
#define when_xx(mp_pred , mp_line) \
monitor::lock_g uard lock_guard_##mp _line(*this); \
monitor::signal _guard signal_guard_## mp_line(*this); \
while (! (mp_pred)) this->wait();
#define when_x(mp_pred, mp_line) when_xx(mp_pred , mp_line)
#define when(mp_pred) when_x(mp_pred, __LINE__)
/* Simple Example
_______________ _______________ _______________ _______________ __*/
#include <string>
#include <deque>
#include <cstdio>
template<typena me T>
struct bounded_buffer : monitor {
unsigned const m_max;
std::deque<Tm_b uffer;
public:
bounded_buffer( unsigned const max_) : m_max(max_) {}
void push(T const& obj) {
when (m_buffer.size( ) < m_max) {
m_buffer.push_b ack(obj);
}
}
T pop() {
when (! m_buffer.empty( )) {
T obj = m_buffer.front( );
m_buffer.pop_fr ont();
return obj;
}
}
};
struct person : thread_base {
typedef bounded_buffer< std::stringqueu e;
std::string const m_name;
queue& m_response;
public:
queue m_request;
void on_active() {
m_response.push (m_name + " is ready to receive some questions!");
for (unsigned i = 0 ;; ++i) {
std::string msg(m_request.p op());
if (msg == "QUIT") { break; }
std::printf("(Q )->%s: %s\n", m_name.c_str(), msg.c_str());
switch (i) {
case 0:
msg = "(A)->" + m_name + ": Well, I am okay";
break;
case 1:
msg = "(A)->" + m_name + ": I already told you!";
break;
default:
msg = "(A)->" + m_name + ": I am PISSED OFF NOW!";
}
m_response.push (msg);
}
std::printf("%s was asked to quit...\n", m_name.c_str()) ;
m_response.push (m_name + " is FINISHED");
}
person(std::str ing const& name, queue* q, unsigned const bound)
: m_name(name), m_response(*q), m_request(bound ) {}
};
#define BOUND 10
int main(void) {
{
person::queue response(BOUND) ;
active<personch ris("Chris", &response, BOUND);
active<personam y("Amy", &response, BOUND);
std::printf("%s \n", response.pop(). c_str());
std::printf("%s \n\n", response.pop(). c_str());
chris.m_request .push("How are you doing?");
amy.m_request.p ush("How are you feeling?");
std::printf("%s \n", response.pop(). c_str());
std::printf("%s \n\n", response.pop(). c_str());
chris.m_request .push("Do you really feel that way?");
amy.m_request.p ush("Are you sure?");
std::printf("%s \n", response.pop(). c_str());
std::printf("%s \n\n", response.pop(). c_str());
chris.m_request .push("Why do you feel that way?");
amy.m_request.p ush("Can you share more of you feelings?");
std::printf("%s \n", response.pop(). c_str());
std::printf("%s \n\n", response.pop(). c_str());
chris.m_request .push("QUIT");
amy.m_request.p ush("QUIT");
std::printf("%s \n", response.pop(). c_str());
std::printf("%s \n", response.pop(). c_str());
}
std::puts("\n\n \n_____________ _____\nhit <ENTERto exit...");
std::fflush(std out);
std::getchar();
return 0;
}
_______________ _______________ _______________ _______________ _________
Please correct me if I am wrong, but Boost would force me to dynamically
create the `person::queue request' object in main right? AFAICT, this
example shows why is can be a good idea to treat a thread as an object. In
this case, a person object is a thread. Anyway, as of now, I am not entirely
convinced that Boost has a far superior method of creating threads...
Anyway, I really do need to think about the rest of your post; you raise
several interesting issues indeed.