Search code examples
c++if-statementc++17lockingmutex

Difference between if statement initializers taking a lock


I need to get a lock only during evaluation of the if statement condition.

The lock must be released when running the code inside the if or else statements.

I found a piece of code and I don't know if it works. I'm rather new to C++, so please forgive me if this is an obvious question.

Example 1

I found I cannot use this if statement initializer:

if (std::lock_guard lock(mutex); condition) {
  // do something
}

as it will be equivalent to the following, which will hold the lock also during execution of the if or else blocks:

{
    std::lock_guard lock(mutex);
    if (condition) {
      // do something
    }
}

Example 2

I also found something like this:

if (std::lock_guard(mutex); condition) {
  // do something
}

This will definitely not work. It will create the lock and then directly destroy it (and free the lock) as the lock_guard is not stored in a variable.

So in fact, this example will not hold the lock when condition is evaluated, and also not hold the lock during execution of the if or else blocks.

Example 3

However, I also found this code:

if (std::lock_guard{mutex}, condition) {
    // do something
}

Note that instead of a ; there is a , and also the brackets differ, {} instead of ().

This code compiles and run.

I verified that the lock is not held during execution of the if or else blocks, because the following does not dead-lock:

if (std::lock_guard{mutex}, condition) {
    std::lock_guard lock(mutex);
    // do something
}

Questions

Does the code in example 3 work as intended? That is, does it hold a lock during evaluation of condition? I cannot find out if this line actually works. I'm afraid it may be similar to example 2.

Does this type of if initializer have a name so I can read up on it?


Solution

  • Yes, example 3 works as you want, but it's highly obscure

    What you found is not any special initialisation, it's the comma operator. Comma operator evaluates left hand side operand, discards it (although the object is not destroyed until end of expression), then evaluates right hand side and returns it. So std::lock_guard{mutex}, condition creates temporary lock_guard, ignores it, evaluates condition and destroys temporary lock_guard.

    However, comma operator is a really obscure feature and it's hard to understand. It's also hard to notice, since comma is usually used in other contexts. I'd strongly recommend to simply extract a function

    bool condition() {
        std::lock_guard lock {mutex};
        return /*evaluate the condition here*/;
    }
    

    and call it in if:

    if (condition()) {
        // mutex isn't held here
    } else {
        // nor here
    }
    

    Important note

    I verified that the lock is not held during execution of the if or else blocks because the following does not dead-lock:

    if (std::lock_guard{mutex}, condition) {
        std::lock_guard lock(mutex);
    // do something 
    }
    

    If mutex was locked here, you have Undefined Behaviour (because the thread that holds a lock on mutex attempts to lock it again), so this doesn't test anything.