Boost logo

Boost :

From: Bronek Kozicki (brok_at_[hidden])
Date: 2004-07-22 15:52:57


Howard Hinnant wrote:

>> * I do not quite like
>> "write_lock(try_rvalue_ref<upgradable_read_lock<mutex_type> > r);"
>> (neither
>> "exclusive_lock(try_rvalue_ref<upgradable_shared_lock<mutex_type> >
>> r)" ). It seems inconsistent with rest of interface. What about
>> "write_lock (rvalue_ref<upgradable_read_lock<mutex_type> > r,
>> blocking_enum block_it);" ?
>
>
> My problem is that this syntax does not generalize to the
> try-move-from-upgradable_lock-assignment:
>
> upgradable_lock ul(m);
>
> scoped_lock sl (try_move(ul));
> sl = try_move(ul);
>
> I really want the try-to-upgrade syntax to be consistent between copy
> ctor and assignment forms. The consistency makes the interface easier
> to learn.

I'm little nervous seeing "try_move". If move semantics (the one you
proposed in N1377) gets into language, "move" can be replaced with
simple move-constructor, while "try_move" cannot. Moreover, there is
already constructor of scoped_lock) taking two parameters, second of
them being enumeration (or bool - I'm not in favour of either) meaning
"this operation may block". Thus, under my proposal we would have:

upgradable_lock ul(m1); // blocking
scoped_lock s2(m2); // blocking
scoped_lock sl(move(ul)); // blocking
scoped_lock s3(m3, try_lock); // non-blocking (A)

upgradable_lock u4(m4); // blocking
scoped_lock s4(move(u4), try_lock); // non-blocking (B)

I think that you will agree that A and B are similar and consistent?

> I went with Peter's non-const pointer.

I think that additional const member functions:

template <typename Mutex>
bool same_mutex(const scoped_lock<Mutex>&) const;

template <typename Mutex>
bool same_mutex(const upgradable_lock<Mutex>&) const;

template <typename Mutex>
bool same_mutex(const read_lock<Mutex>&) const;

are not useful any more?

> I'm not completely against the free functions. But I do feel that they
> are syntax sugar for the constructors that must be there anyway
> (discussed more below). And if you have them for one lock, you need
> them for all 3 locks for consistency.

users of all locks could use the same templated functions:

template <template <typename _Mutex> class Lock, typename Mutex>
Lock<Mutex> lock(Mutex&);

template ....
Lock<Mutex> try_lock(Mutex&);

template ....
Lock<Mutex> try_lock(Mutex&, const elapsed_time&);

>
>> * I'd like to have common base class over all lock classes. I do not
>> want polymorphic behaviour (all member functions might be protected,
>> including destructor, ctor and cctor), but to allow following very
>> simple and I believe typical usage scenarios:
>
>
> The common base class stuff makes me nervous. I don't want polymorphic
> behavior either. But if it isn't polymorphic behavior what would
> ~lock() do? Choices are m_.unlock(), m_.unlock_sharable() and
> m_.unlock_upgradable().

destructor would be protected (as constructor, cctor and assignment) and
do nothing. There's no special need for any virtual functions, including
destructor. This base class would be useful to accept and use reference
to lock value returned by helper functions:

   // we do not know type of some_synchronization_primitive here
   lock_base& l = try_lock<scoped_lock>(some_synchronization_primitive);
   if (l) {
     // ...
   }

It would provide very limited set of non-virtual member functions:
   bool locked() const;
   operator safe_bool() const;

and maybe some way to test identity of mutex with other lock. We could
use it when using generic helper functions to create lock from unknown
(at the time of writing code) type of mutex, as demonstrated above.

> I don't dislike these helper functions. But I do dislike the idea that
> they should be the only way to construct-and-try or
> construct-with-time.

The whole thing with helper functions is that we minimize number of
constructors, instead providing functions. Take a look at following code:

{
   exclusive_lock some_lock(some_mutex);
   // some_mutex is not locked yet; some_lock merely points to it
   some_lock.lock();
   // some_mutex is locked now
} // after destruction of some_lock, some_mutex is unlocked again

Here exclusive_lock would have only one, nonblocking constructor. All
blocking operations would be performed "by hand" using member functions,
  or in template helper functions (like try_lock above).

I know this is very different design. Just one nonblocking constructor,
number of simple functions (mostly forwarding requests to
synchronization primitive and remembering returned state of it when
necessary) and destructor required to free lock. Move-constructors and
move-assignment (as well as regular cctor and assignment in case of
shared lock) are great ideas, also upgradable_lock has its place in such
design.

Best regards

B.


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