Lambda based "lock_guard": std::locked

2,478 views
Skip to first unread message

Ben Craig

unread,
Nov 13, 2012, 9:25:16 PM11/13/12
to std-pr...@isocpp.org
I want to be able to do this in C++1y:

std::mutex m;
std
::locked(m, [&]{
   
//modify shared state
});

Something like "std::locked" solves a problem I have seen many times.  People intend to write "std::lock_guard<std::mutex> lock(m);", but instead write "std::lock_guard<std::mutex>(m);".  The second construct locks and immediately unlocks the mutex, protecting nothing.  Ideally, compilers would warn about unused temporary objects, but I haven't used a compiler that does.  With "std::locked", there is no need to name the guard.

It also solves an aesthetic issue. With std::locked, I don't need to name the Lockable type every time I want to lock the object, where with lock_guard, I get to mention the type repeatedly.

So here's the very rough implementation of std::locked:

template <typename Lockable, typename Func>
void locked(Lockable &lockable, Func func) {
   lock_guard
<Lockable> local_lock(lockable);
   func
();
}

Now, if we want to request a language feature, and not just a library feature, it would be nice to have the variadic template version:
template <typename ...LockableN, typename Func>
void locked(LockableN &...lockableN, Func func) { //leading variadic parameters not currently legal
   std
::lock(lockableN...);
   
try {func();}
   
catch(...) {std::unlock(lockableN...);} //std::unlock doesn't currently exist
   std
::unlock(lockableN...);
}

But to be honest, I'd love to see the single lock version anyway.  It is likely I will be implementing the single lock version locally if it doesn't become standardized.

Anthony Williams

unread,
Nov 14, 2012, 3:09:35 AM11/14/12
to std-pr...@isocpp.org
On 14/11/12 02:25, Ben Craig wrote:
> I want to be able to do this in C++1y:
>
> |
> std::mutex m;
> std::locked(m,[&]{
> //modify shared state
> });
> |

That's an interesting idea. It's an example of the "Execute Around
Object" pattern, which is a nice counterpart to RAII.

I'm not sure "locked" is the right name. lock_and_call seems slightly
more appropriate, but unwieldy.

Anthony
--
Author of C++ Concurrency in Action http://www.stdthread.co.uk/book/
just::thread C++11 thread library http://www.stdthread.co.uk
Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

Ville Voutilainen

unread,
Nov 14, 2012, 3:14:08 AM11/14/12
to std-pr...@isocpp.org
On 14 November 2012 10:09, Anthony Williams <antho...@gmail.com> wrote:
> That's an interesting idea. It's an example of the "Execute Around
> Object" pattern, which is a nice counterpart to RAII.
> I'm not sure "locked" is the right name. lock_and_call seems slightly
> more appropriate, but unwieldy.

Bikeshed time! locked_call?

Dean Michael Berris

unread,
Nov 14, 2012, 3:18:03 AM11/14/12
to std-pr...@isocpp.org
How about "synchronized"? :P

--
Dean Michael Berris | Software Engineer
Google

Ben Craig

unread,
Nov 14, 2012, 8:23:20 AM11/14/12
to std-pr...@isocpp.org
My preferred name is actually "std::lock", but that is already taken.  If someone braver, and more adept at variadic templates + sfinae wants to take a stab at making an additional std::lock overload, then they are more than welcome.

Vicente J. Botet Escriba

unread,
Nov 14, 2012, 12:55:41 PM11/14/12
to std-pr...@isocpp.org
Le 14/11/12 03:25, Ben Craig a écrit :
I want to be able to do this in C++1y:

std::mutex m;
std
::locked(m, [&]{
   
//modify shared state
});

It also solves an aesthetic issue. With std::locked, I don't need to name the Lockable type every time I want to lock the object, where with lock_guard, I get to mention the type repeatedly.
Yes this is nice.


Your function seems to cover almost the same needs. For the function name I would prefer std::synchronize. It would be great to do some performances comparisons between std::locked and the direct use of std::lock_guard.

I use to use a language-like macro.

#define synchronized(MUTEX) \
  if (bool _stop_ = false) {} else \
  for (std::lock_guard<decltype(MUTEX)> _lock_(MUTEX); 
      !_stop_; _stop_ = true)

The problems with the macro is that it introduces two new variables that could conflict with user's variables. Is there a portable way to define this macro avoiding these conflicting variables?

In addition the macro introduce a lot of boilerplate in order to ensure that it works followed by a C++ statement. It would be great if we could add new syntax to C++ using some kind of transformation.

synchronized(mtx)
  //... STATEMENT

in

{
   lock_guard<std::mutex> _lock_(mtx);  // _lock_ should be a unique identifier that doesn't conflicts with any other identifier.
   //... STATEMENT
}

Next follow the kind of information that will be needed to express this transformation

@frame

@keyword synchronized // declares a new keyword

@grammar statement |= <synchronize-statement> // augment the current definition of a statement

@grammar synchronize-statement ::= synchronized ( <expr:expression> ) <stmt:statement> // defines the new statement

@identifier _lock_ // generates a unique identifier usable only in this transformation

@transformation // defines the transformation

    {
        std::lock_guard<decltype($(expr))> _lock_($(expr));

        $(stmt)
    }

@end_frame

An external tool could do this kind of transformations, but it will be so funny if this could be defined inside the C++ language. Of course, this will be much more expensive than including in the language the synchronize-statement directly.

Sorry for the digression.
Vicente


korcan....@googlemail.com

unread,
Nov 14, 2012, 6:05:25 PM11/14/12
to std-pr...@isocpp.org
That's an interesting idea. It's an example of the "Execute Around
Object" pattern, which is a nice counterpart to RAII.

In functional languages like Lisp, Scheme and Haskell these types of higher-order functions are called "with" functions. In this particular example such a function would be called something like "with_lock". Please lets try to avoid abusing terminology in C++ again, "functor" was bad enough.

Generalizations of "with" functions for which all "with" functions can be defined in terms of these are the functions typically called "bracket". These general RAII style functions typically take 3 parameters; a computation to run first ("acquire resource"), computation to run last ("release resource"), and finally the computation to run in-between.

I prefer with/bracket functions over C++ style RAII in some cases.

korcan....@googlemail.com

unread,
Nov 14, 2012, 6:24:37 PM11/14/12
to std-pr...@isocpp.org, korcan....@googlemail.com
You may also want to return a result from a "with" function, e.g:

#include <mutex>

template <typename Lockable, typename Function>
inline auto with_lock(Lockable &lockable, Function fn) -> decltype(fn())
{
    std
::lock_guard<Lockable> local_lock(lockable);
   
return fn();
}

//...
std
::mutex m;
const bool result = with_lock(m, []() -> bool
{
   
// ...
   
return true;
});


Klaim - Joël Lamotte

unread,
Nov 14, 2012, 7:26:59 PM11/14/12
to std-pr...@isocpp.org, korcan....@googlemail.com
Hi, do you mean

const bool result = with_lock(m, []() -> bool
{
    // ...
    return true;
})();


korcan....@googlemail.com

unread,
Nov 14, 2012, 7:35:55 PM11/14/12
to std-pr...@isocpp.org, korcan....@googlemail.com
Nah, with_lock is not returning a function, the function parameter is being executed in with_lock and what ever the function parameter returns it will be returned by with_lock. Maybe using a boolean return type as an example was a bad example and confusing.

Ben Craig

unread,
Nov 14, 2012, 9:24:43 PM11/14/12
to std-pr...@isocpp.org, korcan....@googlemail.com
I like the name "with_lock", and I like the idea of allowing a return type.  Python also uses the "with" syntax.

I hadn't fully thought out returns within the callable function.  Early returns will make it more difficult for client code to switch from lock_guards to with_lock, as the early return's behavior changes substantially by putting it in a lambda.  At least with the return type on the with_lock function, they can still get information back out without loading up on reference captures.

stackm...@hotmail.com

unread,
Nov 19, 2012, 4:38:28 PM11/19/12
to std-pr...@isocpp.org
What if I want to use the lock in my function, e.g. with a condition_variable? What if I don't want a std::lock_guard, but a std::unique_lock, or something entirely different? What if I want to call try_lock, try_lock_for or try_lock_until to be called, instead of .lock()?

Ben Craig

unread,
Nov 19, 2012, 7:30:39 PM11/19/12
to std-pr...@isocpp.org, stackm...@hotmail.com
This doesn't attempt to solve those use cases, but they don't have the same problems that the main std::lock_guard use case has.  To reiterate from my original post:

Something like "std::locked" solves a problem I have seen many times.  People intend to write "std::lock_guard<std::mutex> lock(m);", but instead write "std::lock_guard<std::mutex>(
m);".  The second construct locks and immediately unlocks the mutex, protecting nothing.
When using a condition_variable, or pretty much anything with a std::unique_lock, you need a variable name.  It is much harder to hit the "unused temporary" problem when you actually need to call a function on the object.

This does leave the aesthetic problem of mutex type repetition for condition_variables and unique_locks.  In my opinion, the best way to solve those is with a core language feature.  Something like this:
std::lock_guard<auto> lock(m); //template type deduced from constructor argument type
//or even better...
std
::lock_guard lock(m); //template in disguise.

Even if such a core language feature were present, I think with_lock would make sense because of the unused temporary issue.

stackm...@hotmail.com

unread,
Nov 19, 2012, 8:24:04 PM11/19/12
to std-pr...@isocpp.org
Why not introduce something similar to Java's synchronized keyword instead?
synchronized(mutex)
{
    // ...
}

// equivalent to:
{
    std
::lock_guard<decltype(mutex)> __lock;
   
// ...
}

This solves your problem nicely and clean, and has the following additional advantages:
  • No unneeded passing of parameters or return values
  • Easier to use, less syntactic noise (ie. no lambda needed)
  • Could be extended to solve mentioned use cases, for example:
synchronized(mutex, custom_lock<std::mutex> x)
{
   
// ...
}

// equivalent to
{
    custom_lock
<std::mutex> x(mutex);
   
// ...
}


stackm...@hotmail.com

unread,
Nov 19, 2012, 8:29:07 PM11/19/12
to std-pr...@isocpp.org
And also, an important point I forgot to mention: It is better visible in source code and you can easily search for it.

Ben Craig

unread,
Nov 19, 2012, 11:46:31 PM11/19/12
to std-pr...@isocpp.org, stackm...@hotmail.com
It is my understanding that library solutions are preferred to core language solutions, if they can be performed reasonably in the existing core language.  Getting a keyword added to the language is also pretty difficult.  Now to address some of the specific points:
* unnecessary parameter passing: I am under the (possibly mistaken) impression that lambdas and template functions inline very well.  In release mode builds, I expect the generated assembly of my proposal to be identical to the handwritten lock_guard approach.  In the coming weeks, I will try to validate this assumption with MSVC2012.
* syntactic noise:  Yes, it is a bit more syntax, but not much in the void return case.  I count five extra characters (comma, semicolon, and [&]).  If a return type is needed, then there is significant noise.
 * extension: with_unique_lock would be easy to propose as well, and that could hand the unique_lock to the lambda.  It doesn't involve standard library extensions for arbitrary lock types, but the function that has been proposed is five lines long.  Maybe there's a way to get a truly generic algorithm with template template parameters though.
* searchable: with_lock is just as searchable as synchronized, perhaps more so since the term "with_lock" is less likely to show up in the comments of legacy code.

Jens Maurer

unread,
Nov 20, 2012, 2:31:37 AM11/20/12
to std-pr...@isocpp.org
On 11/20/2012 02:24 AM, stackm...@hotmail.com wrote:
> Why not introduce something similar to Java's synchronized keyword instead?

> synchronized(mutex)
> {
> // ...
> }
>
> // equivalent to:
> {
> std::lock_guard<decltype(mutex)>__lock;
> // ...
> }

C++ has a tendency to avoid core language extensions if a library
solution will do. range-for is one of the exceptions, but at least
it doesn't need a new keyword.

If a proposal like this comes before the committee, I'll vote against
it absent substantially more compelling reasons than what I've heard
so far.

Jens

stackm...@hotmail.com

unread,
Nov 20, 2012, 4:00:32 AM11/20/12
to std-pr...@isocpp.org
Then IMO there is no point in adding a std::locked-Function, when I can get something more convenient using a macro.

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 2:12:48 PM11/20/12
to std-pr...@isocpp.org
Le 15/11/12 00:24, korcan....@googlemail.com a écrit :
I like the with_ proposal. I would like we compare to an alternative using lock factories.

    std::mutex m;
    // ...
    bool result;
    {
      auto lk = make_lock_guard(m) :
      // ...
      result = true
    }

or using another lock

    {
      auto lk = make_unique_lock(m) :
      // ...
    }

or locking multiple locks at once (See my proposal for make_unique_locks in another post).

    {
      auto lk = make_unique_locks(m1, m2, m3);
      // ...
    }

Note that lock_guard needs to be modified so that it can be created from the result of make_lock_guard.

Vicente



Howard Hinnant

unread,
Nov 20, 2012, 2:26:18 PM11/20/12
to std-pr...@isocpp.org
On Nov 20, 2012, at 2:12 PM, "Vicente J. Botet Escriba" <vicent...@wanadoo.fr> wrote:

> I like the with_ proposal. I would like we compare to an alternative using lock factories.
>
> std::mutex m;
> // ...
> bool result;
> {
> auto lk = make_lock_guard(m) :
> // ...
> result = true
> }

<snip>

> Note that lock_guard needs to be modified so that it can be created from the result of make_lock_guard.

Once you add a move constructor to lock_guard it becomes virtually indistinguishable from unique_lock. It would loose its raison d'être.

Howard

pub...@gmail.com

unread,
Nov 20, 2012, 3:09:47 PM11/20/12
to std-pr...@isocpp.org
This might be a silly idea, but lock_guard could have a call member which did the same thing:

std::lock_guard<std::mutex>(m).call([&](){ ... });

Also, the variadic version could be implemented recursively:

template<typename Func, typename Lockable, typename... Lockables>
void locked(Func func, Lockable lock, Lockables... lockables) {
    lock_guard<Lockable> local_lock(lockable);
    locked(func, lockables...);
}

pub...@gmail.com

unread,
Nov 20, 2012, 3:12:46 PM11/20/12
to std-pr...@isocpp.org, pub...@gmail.com
Actually, there's probably a deadlock in that recursive one so I'll take back what I said.

Alberto Ganesh Barbati

unread,
Nov 20, 2012, 4:34:30 PM11/20/12
to std-pr...@isocpp.org
Just write it like this:

      auto&& lk = make_lock_guard(m);

and you won't need to modify lock_guard.

Ganesh

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 5:07:05 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 20:26, Howard Hinnant a �crit :
> Once you add a move constructor to lock_guard it becomes virtually indistinguishable from unique_lock. It would loose its raison d'�tre.
>
> Howard
>
Hrr, you are right. We can not get

auto lk = make_lock_guard(m);

without making lock_guard movable.

make_lock_guard could return something hidden that behaves as a lock_guard and is movable. Would this be acceptable if there is no performance degradation?

Vicente


Jeffrey Yasskin

unread,
Nov 20, 2012, 5:15:40 PM11/20/12
to std-pr...@isocpp.org
On Tue, Nov 20, 2012 at 2:07 PM, Vicente J. Botet Escriba
<vicent...@wanadoo.fr> wrote:
> Le 20/11/12 20:26, Howard Hinnant a écrit :
>
>> On Nov 20, 2012, at 2:12 PM, "Vicente J. Botet Escriba"
>> <vicent...@wanadoo.fr> wrote:
>>
>>> I like the with_ proposal. I would like we compare to an alternative
>>> using lock factories.
>>>
>>> std::mutex m;
>>> // ...
>>> bool result;
>>> {
>>> auto lk = make_lock_guard(m) :
>>> // ...
>>> result = true
>>> }
>>
>> <snip>
>>
>>> Note that lock_guard needs to be modified so that it can be created from
>>> the result of make_lock_guard.
>>
>> Once you add a move constructor to lock_guard it becomes virtually
>> indistinguishable from unique_lock. It would loose its raison d'être.
>>
>> Howard
>>
> Hrr, you are right. We can not get
>
> auto lk = make_lock_guard(m);
>
> without making lock_guard movable.
>
> make_lock_guard could return something hidden that behaves as a lock_guard
> and is movable. Would this be acceptable if there is no performance
> degradation?

I believe unique_lock will perform the same as lock_guard if you use
it simply and with an optimizing compiler. It would be interesting to
see benchmarks or assembly dumps showing otherwise, but assuming it
does perform the same, I'd just skip make_lock_guard and have people
use make_unique_lock(). They can use "const auto lk" if they want to
declare to readers that they never modify the unique_lock.

I'd also support a proposal to make something like:
auto __ = make_unique_lock(m1);
auto __ = make_unique_lock(m2);
work. (Note that I've used the same variable name twice there. "__"
would denote an explicitly unused variable so that its name can be
re-used. "_" would be better, but it's not currently reserved. This
also means we don't need the 'const' since it's impossible to refer to
the variable later.)

Jeffrey

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 6:02:38 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 22:34, Alberto Ganesh Barbati a écrit :
Thanks for the hint. I've declared make_lock_guard as

  template <typename Lockable>
  lock_guard<Lockable>&& make_lock_guard(Lockable& mtx);

and I'm getting a warning:
../../../boost/thread/lock_guard.hpp:59:12: warning: returning reference to local temporary object [-Wreturn-stack-address]
    return lock_guard<Lockable>(mtx);

which seems correct to me.

Is this the correct way to declare it?

Vicente

Vicente J. Botet Escriba

unread,
Nov 20, 2012, 6:09:04 PM11/20/12
to std-pr...@isocpp.org
Le 20/11/12 23:15, Jeffrey Yasskin a �crit :
> On Tue, Nov 20, 2012 at 2:07 PM, Vicente J. Botet Escriba
> <vicent...@wanadoo.fr> wrote:
>> Le 20/11/12 20:26, Howard Hinnant a �crit :
>>
>>> On Nov 20, 2012, at 2:12 PM, "Vicente J. Botet Escriba"
>>> <vicent...@wanadoo.fr> wrote:
>>>
>>>> I like the with_ proposal. I would like we compare to an alternative
>>>> using lock factories.
>>>>
>>>> std::mutex m;
>>>> // ...
>>>> bool result;
>>>> {
>>>> auto lk = make_lock_guard(m) :
>>>> // ...
>>>> result = true
>>>> }
>>> <snip>
>>>
>>>> Note that lock_guard needs to be modified so that it can be created from
>>>> the result of make_lock_guard.
>>> Once you add a move constructor to lock_guard it becomes virtually
>>> indistinguishable from unique_lock. It would loose its raison d'�tre.
>>>
>>> Howard
>>>
>> Hrr, you are right. We can not get
>>
>> auto lk = make_lock_guard(m);
>>
>> without making lock_guard movable.
>>
>> make_lock_guard could return something hidden that behaves as a lock_guard
>> and is movable. Would this be acceptable if there is no performance
>> degradation?
> I believe unique_lock will perform the same as lock_guard if you use
> it simply and with an optimizing compiler. It would be interesting to
> see benchmarks or assembly dumps showing otherwise, but assuming it
> does perform the same, I'd just skip make_lock_guard and have people
> use make_unique_lock(). They can use "const auto lk" if they want to
> declare to readers that they never modify the unique_lock.
It seems that Alberto has found a way to make it work.

auto&& lk = make_lock_guard(m);

> I'd also support a proposal to make something like:
> auto __ = make_unique_lock(m1);
> auto __ = make_unique_lock(m2);
> work. (Note that I've used the same variable name twice there. "__"
> would denote an explicitly unused variable so that its name can be
> re-used. "_" would be better, but it's not currently reserved. This
> also means we don't need the 'const' since it's impossible to refer to
> the variable later.)

I have suggested the use of ... on other threads to name a unique and
anonymous variable.

auto&& ... = make_lock_guard(m);

Vicente

Howard Hinnant

unread,
Nov 20, 2012, 7:47:46 PM11/20/12
to std-pr...@isocpp.org
I don't see how this is going to work. I've tried it two ways:

This doesn't compile:

#include <iostream>

struct A
{
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
~A() {std::cout << "~A()\n";}
};

A
make_A()
{
return A();
}

int
main()
{
{
auto&& _ = make_A();
std::cout << "Within scope\n";
}
std::cout << "Outside scope\n";
}

test.cpp:14:12: error: call to deleted constructor of 'A'
return A();
^~~
test.cpp:6:5: note: function has been explicitly marked deleted here
A(const A&) = delete;
^
1 error generated.

This compiles but is not a well-formed program:

#include <iostream>

struct A
{
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
~A() {std::cout << "~A()\n";}
};

A&&
make_A()
{
return A();
}

int
main()
{
{
auto&& _ = make_A();
std::cout << "Within scope\n";
}
std::cout << "Outside scope\n";
}

test.cpp:14:12: warning: returning reference to local temporary object [-Wreturn-stack-address]
return A();
^~~
1 warning generated.

~A()
Within scope
Outside scope

unique_lock on the other hand, is well-behaved:

#include <iostream>

struct A
{
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
A(A&&) = default;
A& operator=(A&&) = default;
~A() {std::cout << "~A()\n";}
};

A
make_A()
{
return A();
}

int
main()
{
{
auto&& _ = make_A(); // or auto _ = ...
std::cout << "Within scope\n";
}
std::cout << "Outside scope\n";
}

Within scope
~A()
Outside scope

whether you catch it by auto&& or auto. auto is simpler syntactically.

Howard

Richard Smith

unread,
Nov 20, 2012, 10:03:24 PM11/20/12
to std-pr...@isocpp.org
Here's the trick:

return {};

> }
>
> int
> main()
> {
> {
> auto&& _ = make_A();
> std::cout << "Within scope\n";
> }
> std::cout << "Outside scope\n";
> }

Then this works:

$ ./a.out

Howard Hinnant

unread,
Nov 20, 2012, 10:49:15 PM11/20/12
to std-pr...@isocpp.org
I see, thanks!

So then we would need to change the lock_guard(mutex_type&) constructor to implicit. This could be an acceptable change.

Howard

Alberto Ganesh Barbati

unread,
Nov 21, 2012, 4:44:02 PM11/21/12
to std-pr...@isocpp.org

Il giorno 20/nov/2012, alle ore 23:15, Jeffrey Yasskin <jyas...@googlers.com> ha scritto:

> I'd also support a proposal to make something like:
> auto __ = make_unique_lock(m1);
> auto __ = make_unique_lock(m2);
> work. (Note that I've used the same variable name twice there. "__"
> would denote an explicitly unused variable so that its name can be
> re-used. "_" would be better, but it's not currently reserved. This
> also means we don't need the 'const' since it's impossible to refer to
> the variable later.)

I'm actually thinking about that. I'd go even a bit further, I'd like the following:

auto = expr;

to have the same effect of:

const auto&& __unspecified = expr;

where expr is any expression and __unspecified is some compiler-generated identifier guaranteed to be unique in the scope.

The use of "const auto&&" (as opposed to a simple "auto") allows expr to evaluate to a non-copiable rvalue and/or to potentially avoid an unnecessary move/copy.

Given that it's a mere syntactic shortcut for an already existing feature, it should be easily implementable.

How does it sound?

Ganesh

Ben Craig

unread,
Nov 21, 2012, 5:51:50 PM11/21/12
to std-pr...@isocpp.org
My current preference is for a library based proposal, but it isn't a strong preference.  I like the ease of implementation and explanation, and I like that it treats locking in a similar way as STL algorithms.  I think that if someone sees lambdas used in this use case, they will be much more likely to use lambdas for things like comparator functions.

That being said, the make_unique_lock approach solves the problems I identified, and it doesn't have issues with early returns.  My main two objections are that this is a core language change instead of a library change, and that the syntax is unusual.  Either of "auto = make_unique_lock(m);" or "const auto &&lock = make_unique_lock(m);" would likely get questions from other developers.  If I were a voting member of the standards committee (which I'm not), I wouldn't vote down this proposal.

New C++ developers often need education about RAII objects in general, but with the current syntax, that education can be applied in lots of areas.  This new syntax doesn't look to be as broadly applicable as RAII.  I currently know of two broad use cases with the unused temporary problem, locking, and tracing / logging.  Are these issues severe enough to introduce a new syntax that would only really be used in those areas?

Alberto Ganesh Barbati

unread,
Nov 21, 2012, 6:34:22 PM11/21/12
to std-pr...@isocpp.org

Il giorno 21/nov/2012, alle ore 23:51, Ben Craig <ben....@gmail.com> ha scritto:

> My current preference is for a library based proposal, but it isn't a strong preference. I like the ease of implementation and explanation, and I like that it treats locking in a similar way as STL algorithms. I think that if someone sees lambdas used in this use case, they will be much more likely to use lambdas for things like comparator functions.

Lambda just for comparators? That's silly. Important frameworks in C++ and other languages are already using lambdas (or their equivalent) in interfaces similar to one described in the OP. For example several iOS frameworks use these kind of idioms and I see a definite trend in this direction.

> That being said, the make_unique_lock approach solves the problems I identified, and it doesn't have issues with early returns. My main two objections are that this is a core language change instead of a library change, and that the syntax is unusual. Either of "auto = make_unique_lock(m);" or "const auto &&lock = make_unique_lock(m);" would likely get questions from other developers. If I were a voting member of the standards committee (which I'm not), I wouldn't vote down this proposal.
>
> New C++ developers often need education about RAII objects in general, but with the current syntax, that education can be applied in lots of areas. This new syntax doesn't look to be as broadly applicable as RAII.

This syntax IS RAII. A specific form of it, I agree, where you don't care about the name of the variable, but still RAII. In the cases where it's applicable, it makes the code a bit more readable. Compare this:

std::unique_lock<std::mutex> l(m);

with:

auto = make_unique_lock(m);

Notice that the "auto =" version is also more generic, as the type of m is automatically deduced. (BTW, I didn't forget the std:: prefix... it's not necessary because of ADL.)

> I currently know of two broad use cases with the unused temporary problem, locking, and tracing / logging. Are these issues severe enough to introduce a new syntax that would only really be used in those areas?

State savers and iostream sentries are other use cases.

Ganesh

Ben Craig

unread,
Nov 21, 2012, 7:43:43 PM11/21/12
to std-pr...@isocpp.org


On Wednesday, November 21, 2012 5:34:31 PM UTC-6, Alberto Ganesh Barbati wrote:

Il giorno 21/nov/2012, alle ore 23:51, Ben Craig <ben....@gmail.com> ha scritto:

> they will be much more likely to use lambdas for things like comparator functions.

Lambda just for comparators? That's silly. Important frameworks in C++ and other languages are already using lambdas (or their equivalent) in interfaces similar to one described in the OP. For example several iOS frameworks use these kind of idioms and I see a definite trend in this direction.
I agree.  Also, I made the original post :) .  I specifically called out comparator functions because, in the code that I deal with, comparators are the main C++03 reason to write function objects.  I see comparators and predicates for algorithms like find_if to be a way to ease other developers into lambdas.

This syntax IS RAII. A specific form of it, I agree, where you don't care about the name of the variable, but still RAII. 
Yes, it is RAII.  It doesn't look as much like traditional RAII as the current C++11 unique_lock option.  That doesn't mean it's a deal breaker, just something to keep in mind.

>   I currently know of two broad use cases with the unused temporary problem, locking, and tracing / logging.  Are these issues severe enough to introduce a new syntax that would only really be used in those areas?

State savers and iostream sentries are other use cases.
 Ah, yes.  I've even written a state saver or two.  I'm not 100% sure that iostream sentries qualify though, as they have an operator bool(), and their destructors don't do anything (at least according to this, haven't checked the standard).

Vicente J. Botet Escriba

unread,
Nov 22, 2012, 2:50:16 AM11/22/12
to std-pr...@isocpp.org
Le 22/11/12 00:34, Alberto Ganesh Barbati a �crit :
>> I currently know of two broad use cases with the unused temporary problem, locking, and tracing / logging. Are these issues severe enough to introduce a new syntax that would only really be used in those areas?
> State savers and iostream sentries are other use cases.
>
Just to add some of the discussed recently scope exit

auto = scope(exit);
auto. = scope(success) ;
auto = scope(failure);

and transactions

auto = transaction(...);

The real and practical examples are quite numerous.

Vicente

Mikael Kilpeläinen

unread,
Nov 22, 2012, 4:37:46 AM11/22/12
to std-pr...@isocpp.org

> auto = scope(exit);
> auto. = scope(success) ;
> auto = scope(failure);
>
> and transactions
>
> auto = transaction(...);
>
> The real and practical examples are quite numerous.
>
> Vicente
>

I haven't quite decided how useful i think this would be.. however, if
we want something like this
I wonder if we want to go bit more generic.. let example tell it all..

auto ? = make_whatever();
std::unique_ptr<int> ?( new int );
struct A {
int ? = 1;
};

where ? is marker for unique name.

Then again, maybe it is too confusing and the simple solution is preferred.


Mikael

Alberto Ganesh Barbati

unread,
Nov 20, 2012, 7:06:25 AM11/20/12
to std-pr...@isocpp.org

Il giorno 20/nov/2012, alle ore 02:24, stackm...@hotmail.com ha scritto:

> Why not introduce something similar to Java's synchronized keyword instead?
> synchronized(mutex)
> {
> // ...
> }
>
> // equivalent to:
> {
> std::lock_guard<decltype(mutex)> __lock;

shouldn't that be

std::lock_guard<decltype(mutex)> __lock(mutex);

?

> // ...
> }
>

You have to understand that we can't afford to introduce core features so specialized, which tie with the library so much, all for a little notation gain.

If you want to suggest a core feature, you have to aim at something more general and less coupled with the library.

For example, if you declare the following:

template <class Mutex>
std::lock_guard<Mutex> synchronize(Mutex& m)
{
return std::lock_guard<Mutex>(m);
}

then you could write:

{
auto&& lock = synchronize(mutex);
// ...
}

which would effectively have the same semantic that you propose (by binding the temporary lock_guard to a reference, you effectively extend its lifetime up to end of the scope).

Now, and this could be the base for a nice proposal, if we just could have a syntax to get rid of the "auto&& lock =" while keeping the same semantic... ;-)

Ganesh


Ben Craig

unread,
Nov 27, 2012, 10:08:39 PM11/27/12
to std-pr...@isocpp.org

I have a proposal for the library based approach, very similar to my initial post.  I have attached the html version, and you can view an online version here:
https://docs.google.com/document/d/1hSMTw-N2PxapY0SykgfGAPY41HnTMNs7CHKEcptFdzA/edit

I have likely misrepresented the core-language alternative, so any feedback on that would be appreciated.  The SFINAE for the unique_lock version is a bit rough, so if there are suggestions for that, then that would also be welcome.
with_lock_proposal.html

Daniel Krügler

unread,
Nov 28, 2012, 2:36:07 AM11/28/12
to std-pr...@isocpp.org
2012/11/28 Ben Craig <ben....@gmail.com>


I have a proposal for the library based approach, very similar to my initial post.  I have attached the html version, and you can view an online version here:
https://docs.google.com/document/d/1hSMTw-N2PxapY0SykgfGAPY41HnTMNs7CHKEcptFdzA/edit


In regard to the suggested library P/R ("proposed resolution"): The wording is not acceptable, because the part

namespace detail {
 template <typename T>
 unique_lock<T> &get_unique_lock_ref();
}

looks to me as if it would not belong to the spec but to some prototype implementation.

What you say about

template <typename Mutex, typename Func>
 auto with_lock(Mutex &, Func f) -> decltype(f(detail::get_unique_lock_ref<Mutex>()))

doesn't require detail::get_unique_lock_ref, because you could rewrite that as

template <typename Mutex, typename Func>
 auto with_lock(Mutex &, Func f) -> decltype(f(declval<
unique_lock<Mutex>&>()))

where declval is already part of the library spec.

Further on the part

"
The object f is permitted to return any value."

should be removed, because it has no additional value.

I haven't read much of the remaining proposal.

- Daniel


stackm...@hotmail.com

unread,
Nov 28, 2012, 3:09:13 AM11/28/12
to std-pr...@isocpp.org
You should take your Func-Object by RValue Reference to prevent copying. Your reason why synchronized was discarded is inaccurate.
I'm still against your proposal, Folly gets it right: https://github.com/facebook/folly/blob/master/folly/Synchronized.h

stackm...@hotmail.com

unread,
Nov 28, 2012, 3:10:10 AM11/28/12
to std-pr...@isocpp.org, stackm...@hotmail.com

Ben Craig

unread,
Nov 28, 2012, 9:03:30 AM11/28/12
to std-pr...@isocpp.org
Further on the part

"The object f is permitted to return any value."

should be removed, because it has no additional value.
I will get rid of that.  I suppose the function signature with the decltypes already imply that.
 
template <typename Mutex, typename Func>
 auto with_lock(Mutex &, Func f) -> decltype(f(declval<
unique_lock<Mutex>&>()))

where declval is already part of the library spec.
doesn't require detail::get_unique_lock_ref, because you could rewrite that as

When prototyping, I thought I tried exactly that.  But I just tried it again, and now it works.  I will update the spec, as I prefer that approach.


You should take your Func-Object by RValue Reference to prevent copying.
The other standard library functions accept their function objects by value.  It may make sense to update them all to use rvalue / "universal" references.  Note that current conventional wisdom is to make function objects cheap to copy.
I was not aware of that class / macro.  I will stare at that code for a while and figure out if it makes sense to use some of their concepts.  It certainly looks interesting.

Your reason why synchronized was discarded is inaccurate.
Which part of the reasoning / information is inaccurate?  Note that this is specifically rejecting a synchronized keyword, and not a Folly like Synchronized class / macro.


Vicente J. Botet Escriba

unread,
Nov 28, 2012, 12:51:22 PM11/28/12
to std-pr...@isocpp.org
Le 28/11/12 09:09, stackm...@hotmail.com a �crit :
How? Are you suggesting to define a macro?

--Vicente

Vicente J. Botet Escriba

unread,
Nov 28, 2012, 1:32:38 PM11/28/12
to std-pr...@isocpp.org
Le 28/11/12 08:36, Daniel Krügler a écrit :
2012/11/28 Ben Craig <ben....@gmail.com>

I have a proposal for the library based approach, very similar to my initial post.  I have attached the html version, and you can view an online version here:
https://docs.google.com/document/d/1hSMTw-N2PxapY0SykgfGAPY41HnTMNs7CHKEcptFdzA/edit



Hi,

I don't know if the motivating example is a good one to show the advantages of your library.

The problem with

  std::lock_guard<std::mutex>(m);

is a general RAII problem, and if it is enough important, every RAII in the C++ standard should be need to be considered.

The need to use artificial scopes seems more in face with your proposal

type var;
{
    std::lock_guard<std::mutex>(m);
    var = access_to_protected_data();
}

I think the proposal should show how the we can do it without with_lock

std::mutex m;

int q;
{
  std::lock_guard<std::mutex> lk(m);
  ++global_count;
  q = global_count;
}

std::condition_variable cond_var;
bool notified = false;
{
  std::unique_lock<std::mutex> lk(m);
  while (!notified)
  cond_var.wait(l);
}


You could move the reference implementation to a specific section.

The fact that we can not return from the protected function seems a big drawback compared to the current solution

E.g. should the user continue to use lock_guard in this context?

{
    std::lock_guard<std::mutex>(m);
    if (cnd) return access_to_protected_data();
    another_access_to_protected_data();
}


Before the unnamed variable make_lock, there is the alternative to use a named one

std::mutex m;
int q;
{
  auto && lk = std::make_lock_guard(m);
  ++global_count;
  q = global_count;
}

std::condition_variable cond_var;
bool notified = false;
{
  auto lk = std::make_unique_lock(m);
  while (!notified)
  cond_var.wait(l);
}


I have been thinking to mixing the *unnamed variable* and a *let-in statement* to make it possible

std::mutex m;
int q;
auto&& ... = std::make_lock_guard(m) : {
  ++global_count;
  q = global_count;
}

Note the use of ... for the unnamed variable and the colon ':' to introduce a variable in a statement.

std::condition_variable cond_var;

bool notified = false;
auto lk = std::make_unique_lock(m) : {
  while (!notified)
  cond_var.wait(l);
}


I will create a new post to discuss about this language based alternative, that of course has the disadvantage to need two language evolutions :(

-- Vicente

stackm...@hotmail.com

unread,
Nov 28, 2012, 2:01:30 PM11/28/12
to std-pr...@isocpp.org
Am Mittwoch, 28. November 2012 18:51:22 UTC+1 schrieb viboes:
Le 28/11/12 09:09, stackm...@hotmail.com a �crit :
> Folly gets it right:
> https://github.com/facebook/folly/blob/master/folly/Synchronized.h
>
How? Are you suggesting to define a macro?

--Vicente
Yes, this macro would be 10 times better at least. Along with UNSYNCHRONIZED, TIMED_SYNCHRONIZED, etc.

Ben Craig

unread,
Nov 28, 2012, 10:01:01 PM11/28/12
to std-pr...@isocpp.org

I don't know if the motivating example is a good one to show the advantages of your library.

The problem with

  std::lock_guard<std::mutex>(m);

is a general RAII problem, and if it is enough important, every RAII in the C++ standard should be need to be considered.

It is a problem with a subset of RAII classes.  It is a problem with RAII classes with destructor side-effects, where common usage patterns don't use the name elsewhere.  lock_guard is one such class, but unique_lock isn't as much of a problem, since typical usages of unique_lock will use the lock again.  In addition, lock_guard is particularly problematic in that the program will appear to work, until mysterious race conditions start hitting.  To your point though, I will modify my proposal to show how to do this correctly with a named lock.


I can mention the make_lock_guard approach.  I am not a fan of that approach because of the unusual auto && syntax.  I can mention the make_unique_lock approach.  I sort of like that one, as the syntax isn't as gross, and it does solve the specific problems mentioned.  It is also purely library based.  I will probably just shorten it to "make_lock" though.

The language alternatives look interesting, but I will leave those proposals to you.  I've gotten brave enough to propose new library features, but I haven't gotten brave enough to propose core language features yet.

Ben Craig

unread,
Nov 28, 2012, 10:13:21 PM11/28/12
to std-pr...@isocpp.org, stackm...@hotmail.com

Yes, this macro would be 10 times better at least. Along with UNSYNCHRONIZED, TIMED_SYNCHRONIZED, etc.

A macro based implementation has similar problems to a keyword based approach in the amount of code that it breaks.  Macros don't obey namespaces.  If C++1y were to use Folly's approach, and used the same names as Folly, then it would almost certainly break code that uses Folly.  If it uses different "good" names, it will probably break some other library.  I think the only macro names it could use that wouldn't break code (at least according to the standard) would be names with leading underscores.

I will address Folly in the alternatives section, as it is still a very nice facility.  Some use cases, like locking a vector, are very nice.  Other use cases aren't really improved by the Folly approach though, like locking for the duration of every member function of a class.  Folly's approach is also more invasive.  Your lock and data declarations need to change.  It is harder for locks that aren't packaged with SYNCHRONIZED to be usable by SYNCHRONIZED.  You have to overload op-> in a very unusual way.

stackm...@hotmail.com

unread,
Nov 28, 2012, 11:47:20 PM11/28/12
to std-pr...@isocpp.org, stackm...@hotmail.com
You don't have to tell me the problems of macros, I'm well aware of that. I'm also not saying that we need to introduce these macros. But with current language features it's better than your solution.
To convince users of the standard library to use your facility, you have to show use cases where it helps them write better oder simpler code. I personally have never seen the problem you mentioned happening, plus your facility makes my code more verbose. Why would i want to use it?
Folly's macros however help me write simpler code, by just writing SYNCHRONIZED, instead of some lock_guard (which is much simpler to type), plus i don't have to create an extra mutex, it's packaged together with the object i want to lock. That is a very convenient solution I would definitely want to use as a user.

In my opinion you have either 2 options that would work:
  • Put it into the standard library, using macros
  • Bake it into the core language
Any other options are too verbose and don't have enough advantages for users.

That being said, how about this: add keywords like synchronized, and make them only available if the header <synchronized> is included. Allow code to use these keywords as identifiers as long as this header isn't included.
And if you want to make it a library feature: Prefix these macros by STD_

Tony V E

unread,
Nov 29, 2012, 12:07:08 PM11/29/12
to std-pr...@isocpp.org
On Wed, Nov 28, 2012 at 10:13 PM, Ben Craig <ben....@gmail.com> wrote:
>
>
> I will address Folly in the alternatives section, as it is still a very nice
> facility. Some use cases, like locking a vector, are very nice. Other use
> cases aren't really improved by the Folly approach though, like locking for
> the duration of every member function of a class. Folly's approach is also
> more invasive. Your lock and data declarations need to change. It is
> harder for locks that aren't packaged with SYNCHRONIZED to be usable by
> SYNCHRONIZED. You have to overload op-> in a very unusual way.
>

I would add that (IMO) the synchronized operator-> is harmful. It
leads to misunderstandings like:

Synchronized<Container> container;
...
if (!container->empty()) {
auto item = container->front();
...
}

hey the container was synchronized, why does this crash (but only rarely)?

Maybe this is programmer error, but I think we have enough foot-guns
already. If it was my library, I'd only offer the SYNCHRONIZED macro,
not the single-function shortcuts.

Tony

Vicente J. Botet Escriba

unread,
Nov 29, 2012, 5:37:47 PM11/29/12
to std-pr...@isocpp.org
Le 29/11/12 18:07, Tony V E a �crit :
Yes, there is an error here. Do you mean that the error would be more
visible if a synchronize function was used instead, as in

if (!container.synchronize().empty()) {
auto item = container.synchronize().front();
...
}

Vicente

P.S. There is an interesting article "Enforcing Correct Mutex Usage with Synchronized Values" from Anthony William here http://www.drdobbs.com/cpp/enforcing-correct-mutex-usage-with-synch/225200269. Boost.Thread has in trunk a synchronized_value based on this paper which should be delivered on Boost.1.53.


Vicente J. Botet Escriba

unread,
Nov 29, 2012, 5:56:30 PM11/29/12
to std-pr...@isocpp.org
Le 29/11/12 05:47, stackm...@hotmail.com a écrit :


Am Donnerstag, 29. November 2012 04:13:21 UTC+1 schrieb Ben Craig:

Yes, this macro would be 10 times better at least. Along with UNSYNCHRONIZED, TIMED_SYNCHRONIZED, etc.

A macro based implementation has similar problems to a keyword based approach in the amount of code that it breaks.  Macros don't obey namespaces.  If C++1y were to use Folly's approach, and used the same names as Folly, then it would almost certainly break code that uses Folly.  If it uses different "good" names, it will probably break some other library.  I think the only macro names it could use that wouldn't break code (at least according to the standard) would be names with leading underscores.

I will address Folly in the alternatives section, as it is still a very nice facility.  Some use cases, like locking a vector, are very nice.  Other use cases aren't really improved by the Folly approach though, like locking for the duration of every member function of a class.  Folly's approach is also more invasive.  Your lock and data declarations need to change.  It is harder for locks that aren't packaged with SYNCHRONIZED to be usable by SYNCHRONIZED.  You have to overload op-> in a very unusual way.
You don't have to tell me the problems of macros, I'm well aware of that. I'm also not saying that we need to introduce these macros. But with current language features it's better than your solution.
So you are not proposing the introduction of a new macro 'synchronized'.

To convince users of the standard library to use your facility, you have to show use cases where it helps them write better oder simpler code. I personally have never seen the problem you mentioned happening, plus your facility makes my code more verbose. Why would i want to use it?
Folly's macros however help me write simpler code, by just writing SYNCHRONIZED, instead of some lock_guard (which is much simpler to type), plus i don't have to create an extra mutex, it's packaged together with the object i want to lock. That is a very convenient solution I would definitely want to use as a user.
The fact of packaging a mutex with the data it protects is independent from how the mutex is locked. We could suggest the addition of a synchronized_value template class to the C++standard [1], but this will not address the PO concern.

In my opinion you have either 2 options that would work:
  • Put it into the standard library, using macros
I've not see any library proposal using macros. maybe we need a first time.

  • Bake it into the core language
Humm, I don't think we can change the core language each time the language could respond better to a need. C++ is already too big and needs more important features that will make it even bigger.

Any other options are too verbose and don't have enough advantages for users.
What do you think of the alternative

{
  auto lk = synchronize(m);
  // access to protected data

}

That being said, how about this: add keywords like synchronized, and make them only available if the header <synchronized> is included. Allow code to use these keywords as identifiers as long as this header isn't included.
conditional keywords? This doesn't change the fact that you are adding a keyword, even if local to compilation unit.

And if you want to make it a library feature: Prefix these macros by STD_
I would accept a macro STD_SYNCHRONIZED if there is no better option. See my post on Let in statement and unamed variables which could allow you to write

auto = synchronize(m) :
{
  // access to protected data
}

without introducing any new keyword.

Vicente


[1]  "Enforcing Correct Mutex Usage with Synchronized Values" from Anthony William here http://www.drdobbs.com/cpp/enforcing-correct-mutex-usage-with-synch/225200269.

Tony V E

unread,
Dec 4, 2012, 11:54:34 PM12/4/12
to std-pr...@isocpp.org
On Thu, Nov 29, 2012 at 5:37 PM, Vicente J. Botet Escriba
<vicent...@wanadoo.fr> wrote:
> Le 29/11/12 18:07, Tony V E a écrit :
>
>> On Wed, Nov 28, 2012 at 10:13 PM, Ben Craig <ben....@gmail.com> wrote:
>>>
>>>
>> I would add that (IMO) the synchronized operator-> is harmful. It
>> leads to misunderstandings like:
>>
>> Synchronized<Container> container;
>> ...
>> if (!container->empty()) {
>> auto item = container->front();
>> ...
>> }
>>
>> hey the container was synchronized, why does this crash (but only rarely)?
>>
>> Maybe this is programmer error, but I think we have enough foot-guns
>> already. If it was my library, I'd only offer the SYNCHRONIZED macro,
>> not the single-function shortcuts.
>>
>>
> Yes, there is an error here. Do you mean that the error would be more
> visible if a synchronize function was used instead, as in
>
> if (!container.synchronize().empty()) {
> auto item = container.synchronize().front();
> ...
> }
>
> Vicente
>

Yes, that would hopefully make it more obvious.
I might go one further and force them to use STD_SYNCHRONIZE or
whatever it may be. Something that leans towards a *block* not a
single line. I don't like single-line lock code. I *like* that a
lock is indented and has { ... } brackets. Makes it easier to see,
and I think clearly seeing when locks are held and not held, what code
is protected and what is not, is vital.

Tony

Vicente J. Botet Escriba

unread,
Dec 8, 2012, 8:19:03 AM12/8/12
to std-pr...@isocpp.org
Le 05/12/12 05:54, Tony V E a �crit :
> On Thu, Nov 29, 2012 at 5:37 PM, Vicente J. Botet Escriba
> <vicent...@wanadoo.fr> wrote:
>> Le 29/11/12 18:07, Tony V E a �crit :
I like the macro approach but I don't think the committee will accept to
start adding macros in the standard. I let you make a concrete proposal.

Vicente
Reply all
Reply to author
Forward
0 new messages