Design

The library is designed by using trait patterns, similar to the standard I/O. That means, that the classes that the users see are not the classes that do all the work. Instead, the real work is deligated to static utility classes - the traits. All the classes that the user see are in fact templates. As a (fictive) example of this, consider the following dummy trait:
    template <typename Foo> 
    class foo_traits  { 
    public: 
      static void init(Foo&) {}
      static void destroy(Foo&) {} 
      static void doit(Foo&) {}
    };
This doesn't really do much, and it shouldn't. It's a fall-back in case a given feature isn't supported. The library dummy traits may do more - in particular, they may throw an exception if you try to create an object representing a feature that isn't avaliable in the underlying thread library.

The dummy trait above will be specialised to work with the specific underlying library, as given an example of here:

    template <> 
    class foo_traits<foo_t> { 
    public: 
      static void init(foo_t& foo)    { foo_init(&foo); }
      static void destroy(foo_t& foo) { foo_destroy(&foo); } 
      static void doit(foo_t& foo)    { foo_doit(&foo); }
    };
This trait does a whole lot more. We access the underlying library's C API foo_init, foo_destroy, and foo_doit via this trait.

Now the dummy trait and the specialised trait are purely static, so they are not of much use to the user. We'd like to be able to create mutex, thread, etc. objects, and use those. Also, we'd might like to derive from some class that makes these things avaliable, so we can do special stuff. For this to happen, the library defines basic classes of the concepts that the user would like to have. Such a class may look like:

    template <typename Foo, typename Trait=foo_traits<Foo> >
    class basic_foo { 
    public: 
      typedef Foo   foo_type;
      typedef Trait traits_type;
    private: 
      foo_type _foo; 
    public: 
      basic_foo()          { traits_type::init(_foo); }
      virtual ~basic_foo() { traits_type::destroy(_foo); }
      void doit()          { traits_type::doit(_foo); }
    };
So here's the real thing. The class is templated by the library type, and it uses the (possibly specialised) trait on that type. It also contains the state corresponding to the underlying concept.

The user can now use the basic_foo class as

    basic_foo<foo_t> my_foo; 
However, as foo_t depends on the underlying library, and that there'll only be one valid Foo parameters for a given library, the header files does a typedef to make the interface even easier to deal with:
    #ifndef THREAD_foo 
    #define THREAD_foo 

    #include <threadmm/basic/foo.hh> // defined basic_foo<Foo> 
    #ifdef HAVE_my_h 
    #include <threadmm/my/foo.hh> // Defined foo_traits<foo_t>
    typedef basic_foo<foo_t> foo;
    #endif
and now the user can simply do
    foo my_foo;

If you substitute Foo and foo in the above with mutex, thread, condition, etc. and add some specific member functions, then you basically have the Thread-- library!

Note, that this design means that there's no inheritance, and hence no expensive virtual member function calls - the class hierarchy is completly flat. It does mean that the compiler has to generated more code, as it has to make specialised code for all the implecit and explcit template specialisations used in the code. However, as one will only use one thread back-end at a time, this is not really a problem. Also, who gives a ... if your program takes up 100kB or 200kB of RAM - most people will have at least 64MB in this day and age (fall 2002).

There's one class that's notably different - that's the class threadmm::thread_specific. That's because it's not possible to do the typedef trick with partially specialised template classes i.e., what we'd like to do is something like

    template <typename Data, typename Mutex, typename Key, 
              typename Trait=thread_specific_traits<Mutex, Key> > 
    class basic_thread_specific {
       ... 
    public: 
      Data* operator->() { ... }
    }
    template<>
    class thread_specific_traits<posix_mutex, pthread_key_t> { 
      ... 
    }; 
    template <typename Data> 
    typedef thread_specific<Data,posix_mutex,pthread_t> thread_specific<Data>;
or something like that. That isn't possible as far as I know, so instead, I employed a trick, where I use preprocessor macros as the default values of the template parameters. Not nice, but what can you do? (Suggestions are welcome - patches even more!)
Top of page
Last update Tue Nov 9 12:40:50 2004
Christian Holm
Created by DoxyGen 1.3.9.1