Boost logo

Boost :

From: Greg Colvin (gcolvin_at_[hidden])
Date: 2001-06-22 01:28:18


From: Ross Smith <ross.s_at_[hidden]>
> Greg Colvin wrote:
> >
> > If one has only the non-copyable thread object then one
> > is tempted to traffic in raw pointers to that object, with
> > either the usual risks of dangling pointers or else the
> > requirement to keep the thread object around forever.
>
> Why do you see this as a problem specific to threads? It's exactly the
> same for iostreams, and for that matter for anything else that's
> non-copyable. Making objects non-copyable when the concept they
> represent isn't naturally copyable is generally considered a _good_
> thing by most C++ programmers.

I'm not sure how similar threads are to file streams. For
files the typical use is to open a file, read and/or write,
then close the file, which fits well with scoped construction
and destruction.

For threads the typical use is to spawn a thread and forget
about it, with the expectation that it will do whatever it
needs to do and go away on its own when it is done. So it
is harder to bind a thread object to a scope, and thus harder
to ensure that pointers to the thread obejct remain valid.

> As for being "tempted to traffic in raw pointers", surely this is
> exactly the sort of problem std::auto_ptr and boost::shared_ptr were
> created to solve?

Yes, and that might be one way to solve it. I think the catch
is that if you have created a thread and retained no references
to it you may want it just go away when its function exits.

> Similarly, why have a special create() function instead of an ordinary
> constructor, when constructors work perfectly well for every other
> non-copyable entity (iostreams etc.)?

I was just following Beman's interface, but a static create
function is one way to avoid leaking raw pointers and to
encapsulate resource management, and also to avoid the catch
above (warning -- uncompiled example code, more or less for
Windows as I remember it):

    class thread : noncopyable
    {
        friend class thread_ref;
 
        // All constructors private, so create
        // is the only way to create a thread
        thread(void (*threadfunc)(void*), void* param)
        : threadfunc(threadfunc), param(param), self_ptr(this) {
           id = _beginthread(fun,this);
        }
        
        ~thread() { assert(this != &self()); }

        // package up function and parameter for thread creation
        int id;
        void (*threadfunc)(void*);
        void* param;
        static void __cdecl fun(void* arg) {
           thread* p = (thread*)arg;
           try {
              threadfunc(p->param);
           } catch(...) {
           }
           p->self_ptr.reset(0);
        }

        // the thread object is destroyed when there is no thread_ref
        // referring to it and its thread function has returned
        shared_ptr<thread> self_ptr;
 
    public:

        static shared_ptr<thread> create(void (*threadfunc)(void*), void* param)
           return new thread(threadfunc,param));
        }
        static thread & self();
        static void join_all();
        static void sleep(const xtime& xt);
        static void yield();

        bool is_alive() const;
        void join();
    };

Another way is to have copyable thread_ref objects that
allocate and reference-count a non-copyable thread object
behind the scenes, e.g.

    class thread_ref {
       shared_ptr<thread> ptr;
    public:
        thread_ref(void (*threadfunc)(void*), void* param)
        : ptr(thread::create(threadfunc,param)) {}
        bool is_alive() const { return ptr->is_alive(); }
        void join() { ptr->join(); }
    }

If you have thread_ref you would want create(), is_alive()
and join() to be private members of thread, which befriends
thread_ref, with the use of shared_ptr just an implementation
detail.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk