Элегантный переключатель включения / выключения мьютекса для функций?

#c #templates #mutex

#c #шаблоны #мьютекс

Вопрос:

Можете ли вы придумать элегантный способ создания (членских) функций с дополнительным блоком мьютексов? Давайте проигнорируем макросы по понятным причинам.

Конечно, самый простой способ сделать это — использовать две функции:

 int getIndex() const    { std::lock_guard m( mtx ); return nm_getIndex(); }
int nm_getIndex() const { return _index; }
  

Это создает эффективный код, но он основан на дублировании кода. В принципе, в итоге вы получите большинство объявлений функций дважды.

Другим способом было бы превратить их в функции шаблона. Логический аргумент шаблона будет функционировать как «en- / disabler». Затем вы могли бы вызвать функцию следующим образом:

 auto index = getIndex< NoMutex >();
  

всякий раз, когда функция используется внутри (и блокирует мьютекс в противном случае). Проблема здесь в том, чтобы убедиться, что мьютекс разблокирован, даже когда генерируется исключение. То есть вы не можете просто использовать что-то вроде

 if constexpr( MutexOn == true ) {
    mutex.lock();
}
do some stuff;
if constexpr( MutexOn == true ) {
    mutex.unlock();
}
  

Единственное, о чем я могу сейчас думать, это создать класс вокруг мьютекса и поместить его в объединение. Тогда класс может либо «воспроизвести lock_guard», либо ничего не делать. Хотя я почти уверен, что это будет правильно оптимизировано в коде выпуска, он по-прежнему выглядит громоздким и негибким.
Поэтому мне интересно, можете ли вы придумать что-нибудь получше?

Спасибо!

Комментарии:

1. Конечно, существует очевидный метод специализации шаблонов, но это приводит к нескольким методам для 1 действия, как вы сами сказали.

2. Подход «уникальный ptr для защиты блокировки» примерно такой же, как и в объединенной версии, но он добавляет выделение памяти.

Ответ №1:

Самый простой подход, который я могу придумать, это:

  1. Создайте класс noop lock_guard.

  2. Создайте шаблонную функцию, которая всегда использует блокировку типа, указанного в качестве параметра шаблона.

  3. Если вам нужна неблокирующая версия функции, которую вы можете передать в noop guard

Например:

 template<typename Guard>
int getIndex<Guard>() const    { Guard m( lock ); return nm_getIndex(); }

class NoopLockGuard ; //dummy class. it does absolutely nothing
int i = getIndex<NoopLockGuard>()
int j = getIndex<std::lock_guard>()
  

Компилятор должен иметь возможность оптимизировать версию NoopLockGuard таким образом, чтобы она приводила к минимальному или нулевому снижению производительности.

Комментарии:

1. Спасибо, это действительно выглядит красиво. Вы даже можете передавать другие блокировки, такие как блокировки с ограниченным доступом и т. Д. 🙂