tags:

views:

222

answers:

3

Suppose I have code something like this:

#include "boost/thread/mutex.hpp"

using boost::mutex;
typedef mutex::scoped_lock lock;

mutex mut1, mut2;

void Func() {
 // ...
}

void test_raiicomma_1() {
 lock mut1_lock(mut1);
 Func();
}

void test_raiicomma_2() {
 (lock(mut1)), Func();
}

void test_raiicomma_3() {
 (lock(mut1)), (lock(mut2)), Func(); // Warning!
}

int main()
{
 test_raiicomma_1();
 test_raiicomma_2();
 test_raiicomma_3();
 return 0;
}

If the function test_raiicomma_1() were called from multiple threads, it locks a mutex to prevent any other thread also calling Func() at the same time. The mutex is locked when the variable mut1_lock is constructed, and released when it goes out of scope and is destructed.

This works perfectly fine, but as a matter of style, needing to give a name to the temporary object holding the lock irked me. The function test_raiicomma_2() attempts to avoid this, by inialising the lock object and calling the function Func() within one expression.

Is it correct that the temporary object destructor will not be called until the end of the expression, after Func() has returned? (If so, do you think it's ever worthwhile to use this idiom, or is it always clearer to declare the lock in a separate statement?)

If the function test_raiicomma_3() needs to lock two mutexes, is it correct that the mutexes will be locked in order before calling Func(), and released afterwards, but may unfortunately be released in either order?

+3  A: 

While not having to give meaningful names frees you of a burden, it will add the task to find out what that code is supposed to do to the burdens of readers of your code -- atop the task to decide whether test_raiicomma_3 actually works the way its writer seems to have supposed.

Given that a piece of code is written once, but is read 10, 100, or 1000 times, is it really so hard to write all the locks?

sbi
A: 

Mutexes will be created in left-to-right order and released in the end of full expression. You could write:

 // parentheses tells that it is full expression and set order explicitly
 ( ( lock(mut1), lock(mut2) ), Func() );

Mutexes will be destroyed in proper order according to C++ Standard 5.18/1:

The comma operator groups left-to-right.
expression:
assignment-expression
expression , assignment-expression

A pair of expressions separated by a comma is evaluated left-to-right and the value of the left expression is discarded. The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conver- sions are not applied to the left expression. All side effects (1.9) of the left expression, except for the destruction of temporaries (12.2), are performed before the evaluation of the right expression. The type and value of the result are the type and value of the right operand; the result is an lvalue if its right operand is.

Kirill V. Lyadvinsky
Kirill, your quote says "...to which references are bound..." but in this case there are no references. The mutexes are just unnamed temporary objects with no other reference bound to them. This is not to say their destruction happens in a different order but IMHO the quoted section is not applicable here.
LaszloG
No, they are not bound to references. The current Standard is defective in that regard, i suspect ( http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#276 ). It should describe the order at 12.2, but it doesn't so so in C++03, but only talks about reference binding there wrt to the order of destruction.
Johannes Schaub - litb
In the C++0x draft, it says "The destruction of a temporary whose lifetime is not extended by being bound to a reference is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression." which is more general than the c++03's version "In all these cases, the temporaries created during the evaluation of the expression initializing the reference, except the temporary to which the reference is bound, are destroyed at the end of the full-expression in which they are created and in the reverse order of the completion of their construction."
Johannes Schaub - litb
Changed quote to more appropriate. It implicitly states that temporaries in the left expression will be destructed after evaluation of the right expression.
Kirill V. Lyadvinsky
+3  A: 

Is it correct that the temporary object destructor will not be called until the end of the expression, after Func() has returned?

It is guaranteed that both constructor and destructor are called, as they have side effects, and that destruction will only happen at the end of a full expression.

I believe it should work

If the function test_raiicomma_3() needs to lock two mutexes, is it correct that the mutexes will be locked in order before calling Func(), and released afterwards, but may unfortunately be released in either order?

Comma is always evaluated left to right, and automatic variables within a scope are always destroyed in reverse order of creation, so I think it's even guaranteed they are released in the (correct) order too

As litb points out in the comments, you need braces or your expression will be parsed as a declaration.

(If so, do you think it's ever worthwhile to use this idiom, or is it always clearer to declare the lock in a separate statement?)

I don't think so no, to confusing for very, very little gain... I always use very explicit locks, and very explicit scopes (often extra {} within a block), decent threadsafe code is hard enough without 'special' code, and warrants very clear code in my opinion.

YMMW of course :)

Pieter
I found this on msdn regarding the lifetime of temporaries, confirming your answer: http://msdn.microsoft.com/en-us/library/a8kfxa78(VS.80).aspx
xtofl
I think he needs braces. At least GCC seems to parse it as a declaration instead. Although `int(a), Func();` would be a declaration, i think `int(a), int(b), Func();` should be parsed as a comma operator instead of a declaration, but GCC disagrees. Anyway, the parentheses in `(int(a)), (int(b)), Func();` make it non-ambiguous as an expression. Possibly a good idea to wrap it around everything too, like `(int(a), int(b), Func());` may be a bit more readable.
Johannes Schaub - litb
it didn't occur to me a compiler would parse `int(a), int(b), Func();` as a declaration.... updated my answer, thanks for actually trying it in a compiler
Pieter
The trickery is `lock(mut1), lock(mut2), Func();` would be the same as `lock mut1, lock(mut2), Func();` - that is, the first parentheses are redundant, and the second parentheses provide the initializer for the `lock` variable, and the last parentheses declare a function. But GCC parses (wrongly, i think) also `int(a), int(b), Func();` as such a declaration - taking the second `int` as an identifier instead of a keyword during disambiguation. Consequently it later complains `error: expected unqualified-id before 'int'`. If it were to parse it as an expression, then there wouldn't be an error.
Johannes Schaub - litb
So to disambiguate his case, he can wrap each expression separately in parentheses like he did, or wrap the entire comma expression into parentheses. Both ways would make it so that the line cannot be parsed as a declaration anymore.
Johannes Schaub - litb