Условное использование std:: lock_guard

У меня есть функция, в которой оператор foo должен выполняться в lock_guard, но только тогда, когда указатель на объект mutex был предоставлен функции в качестве параметра. В противном случае foo не должен быть защищен lock_guard.

Я не могу использовать lock_guard внутри if, потому что блокировка будет немедленно выпущена, когда заканчивается блок if.

так что этот код вздор:

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
}

Я пробовал что-то вроде этого:

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...
}

Более или менее единственное возможное решение для меня - повторить foo:

bar( std::mutex * optionalMutex = nullptr )
{
   ...
   if ( nullptr != optionalMutex ) {
      std::lock_guard<std::mutex> lockScope( *optionalMutex );
      foo...
   }  else {
      foo...
   }
}

Компилятор gcc 4.9.3 не компилирует второй пример и жалуется: error: expected primary-expression before 'lockScope'. Обновление: Superlokkus объяснил в своем ответе почему.

Но я хочу избежать дубликатов кода и, следовательно, дублировать foo.

Мой вопрос:

Есть ли элегантный способ реализации этой проблемы и не использовать дубликат foo. Я знаю, я мог бы использовать лямбда-функцию для группировки foo, но мне любопытно, есть ли другое решение.

Ответ 1

Как насчет этого?

void bar(std::mutex * optionalMutex = nullptr)
{
        auto lockScope = (optionalMutex == nullptr) ? 
                           std::unique_lock<std::mutex>() 
                         : std::unique_lock<std::mutex>(*optionalMutex);
}

Объяснение: У вашего компилятора были проблемы с вашим предыдущим утверждением, потому что вы не можете внезапно изменить тип тернарного выражения ?; то есть буква 0 не является std::lock_guard и наоборот. Поэтому я изменил две ветки на один и тот же тип, здесь std::unique_lock<std::mutex>, потому что lock_guard не предназначен для использования без действительного мьютекса. Но по-прежнему предпочитайте std::lock_guard более std::unique_lock в более простых случаях, потому что это сделает ваш код более читаемым.

Также ваше утверждение не было жизнеспособным для компилятора, то есть даже синтаксически корректным, поскольку переменная lockScope существовала бы только в одной ветки.

Ответ 2

У вас действительно есть две функции, одна из которых блокируется, а другая - нет. Первый может вызвать второе:

void bar() {
    // whatever
}

void bar(std::mutex* mtx) {
    std::lock_guard<std::mutex> lockScope(*mtx);
    bar();
}

Ответ 3

У меня есть только это решение. Использование фиктивного объекта mutex:

Код:

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
}

Ответ 4

Это второстепенная проблема, но вы можете избежать передачи необработанного указателя, разрешив вызывающему абоненту вместо std:: unique_lock:

bar( std::unique_lock<std::mutex> lockScope )
{
    if(lockScope.mutex())
    {
        lockScope.lock();
        //do things
    }
}

Это похоже на более четкое выражение интерфейса и снижает вероятность злоупотребления.

Ответ 5

Ответ от Superlockus достаточно хорош, но мне интересно, почему вы просто не пишете его так:

bar( std::mutex * optionalMutex = nullptr )
{
   if (optionalMutex)
      optionalMutex->lock():

   foo...

   if (optionalMutex)
      optionalMutex->unlock():
}

lock_guard и unique_lock являются удобными, но не единственным способом.