Reputation: 5186
I have a function where the statement foo
should be executed under lock_guard
but only when a pointer to a mutex
object has been provided to the function as a parameter. Otherwise foo
does not have to be protected by lock_guard
.
I cannot use the lock_guard
within an if
because the lock will be released immediately when the if
block ends.
so, this code is nonsense:
bar( std::mutex * optionalMutex = nullptr )
{
...
if ( nullptr != optionalMutex ) {
std::lock_guard<std::mutex> lockScope( *optionalMutex );
} <- Here the lock ends
foo... <- foo is not protected when optionalMutex was provided
}
I tried something like this:
bar( std::mutex * optionalMutex = nullptr )
{
...
nullptr == optionalMutex ? 0 : std::lock_guard<std::mutex> lockScope( *optionalMutex );
// this scope should be protected by lock_guard when optionalMutex was provided
foo...
}
More or less, the only one possible solution for me is to repeat foo
:
bar( std::mutex * optionalMutex = nullptr )
{
...
if ( nullptr != optionalMutex ) {
std::lock_guard<std::mutex> lockScope( *optionalMutex );
foo...
} else {
foo...
}
}
The compiler gcc 4.9.3
does not compile the 2nd example and complains: error: expected primary-expression before 'lockScope'
. Update: Superlokkus explained in his answer why.
But I do want to avoid any code duplicates and therefore also the duplicate foo
.
My question:
Is there an elegant way how to implement this problem and not to use duplicate foo
. I know, I could use a lambda function to group foo
but I am curious if there is an another solution.
Upvotes: 22
Views: 6969
Reputation: 11
It's fairly trivial to implement your own version of lock_guard which takes a pointer to a mutex rather than a reference.
template <typename MutexT>
class conditional_lock_guard
{
private:
MutexT* const m_mtx;
public:
explicit conditional_lock_guard(MutexT* mtx) :
m_mtx{ mtx }
{
if (m_mtx != nullptr)
{
m_mtx->lock();
}
}
~conditional_lock_guard() noexcept
{
if (m_mtx != nullptr)
{
m_mtx->unlock();
}
}
conditional_lock_guard(const conditional_lock_guard&) = delete;
conditional_lock_guard& operator=(const conditional_lock_guard&) = delete;
bool owns_lock() const noexcept
{
return m_mtx != nullptr;
}
explicit operator bool() const noexcept
{
return owns_lock();
}
MutexT* mutex() const noexcept
{
return m_mtx;
}
};
Upvotes: 1
Reputation: 5049
How about this one?
void bar(std::mutex * optionalMutex = nullptr)
{
auto lockScope = (optionalMutex == nullptr) ?
std::unique_lock<std::mutex>()
: std::unique_lock<std::mutex>(*optionalMutex);
}
Explanation: Your compiler had trouble with your prior statement because, you can not suddenly change the type of the ternary ?
expression; i.e. the literal 0
is not a std::lock_guard
and vice versa. So I changed the two branches to the same type, here std::unique_lock<std::mutex>
because lock_guard
isn't designed be used without a valid mutex. But still prefer std::lock_guard
over std::unique_lock
in the simpler cases, because it will make your code more readable.
Also your statement wasn't viable for the compiler, i.e. even syntactical correct, because the variable lockScope would only have existed in one branch.
Upvotes: 19
Reputation: 880
It's a minor gripe but you can avoid passing the the raw pointer by letting the caller pass a std::unique_lock instead:
bar( std::unique_lock<std::mutex> lockScope )
{
if(lockScope.mutex())
{
lockScope.lock();
//do things
}
}
This seems like a more clear expression of the interface and reduces potential for abuse.
Upvotes: 1
Reputation: 395
The answer from Superlockus is good enough, but I am wondering why you did not simply write it like that:
bar( std::mutex * optionalMutex = nullptr )
{
if (optionalMutex)
optionalMutex->lock():
foo...
if (optionalMutex)
optionalMutex->unlock():
}
lock_guard
and unique_lock
are convenient but not the only way.
Upvotes: 0
Reputation: 76315
What you really have is two functions, one that locks, and one that doesn't. The first one can call the second:
void bar() {
// whatever
}
void bar(std::mutex* mtx) {
std::lock_guard<std::mutex> lockScope(*mtx);
bar();
}
Upvotes: 14
Reputation: 5186
I have only this solution. Using a dummy mutex
object:
The code is:
bar( std::mutex * optionalMutex = nullptr )
{
...
std::mutex dummyMutex;
std::lock_guard<std::mutex> lockScope( optionalMutex ? *optionalMutex, dummyMutex );
foo... <- NOW foo is protected when optionalMutex was provided
}
Upvotes: 1