Проблемы с std::lock_guard<std::mutex> и блокировкой constexpr

Есть шаблон классаFoo<T>. И для какого-то конкретного типа функция должна использоватьlock_guard. Вот пример кода:

      #include <type_traits>
#include <mutex>
#include <vector>

template<typename T> 
class Foo {
public:
    void do_something(int k) {

        if constexpr(std::is_same_v<T, NeedMutexType>) {
            std::lock_guard<std::mutex> lock(mtx_);
        }

        resource_.push_back(k);
        // code for task with resource_ ...
    }

private:
    std::mutex mtx_;
    std::vector<int> resource_;
};

Будет уничтожено в конце области действия if constexpr. (Если это неправда, поправьте меня.)

Чтобы справиться с этим, я могу скопировать код задачи с помощьюresource_ниже в область действия if constexpr или просто используйте необработанныйstd::mutexтакой какmtx_.lock()&mtx_.unlock()вместо.

Есть ли какие-нибудь способы справиться с этим с помощьюstd::lock_guard? Спасибо.

3 ответа

Возможно, здесь на помощь может прийти std::conditional , если вам нужно часто делать подобные вещи.

      template<class Mutex>
struct FakeLockGuard { FakeLockGuard(Mutex&){} };

template<typename T, class Mutex = std::mutex>
using OptionalLock = typename std::conditional<
    std::is_same_v<T, NeedMutexType>,
    std::lock_guard<Mutex>,
    FakeLockGuard<Mutex>>::type;

Здесь мы определили шаблон класса, который ничего не делает, который создан так же, как и . Затем мы используем это сstd::conditionalвыбрать либоstd::lock_guardили в зависимости от результата вашей проверки типа.

Теперь вы можете использовать его следующим образом:

      template<typename T> 
class Foo {
public:
    void do_something(int k)
    {
        OptionalLock<T> lock(mtx_);
        resource_.push_back(k);
        // ...
    }

private:
    std::mutex mtx_;
    std::vector<int> resource_;
};

Вы можете легко убедиться, что это работает, установив точку останова вFakeLockGuardконструктор или заставить его что-то выводить.

Вот как вы можете заставить все это работать во время компиляции. Но я думаю, как вы уже упомянули, вы можете просто построитьunique_lockа затем условно заблокировать его. Преимущество этого подхода заключается в том, что он становится намного понятнее для тех, кто работает с вашим кодом. В конце концов, выбирайте то, что вы считаете наиболее подходящим.

Просто создайте замок в разблокированном состоянии, а затем заблокируйте его позже:

      template<typename T> 
class Foo {
public:
    void do_something(int k) {
        std::unique_lock<std::mutex> lock(mutex_, std::defer_lock);
        if constexpr(std::is_same_v<T, NeedMutexType>) {
            lock.lock();
        }

        resource_.push_back(k);
        // code for task with resource_ ...
    }

private:
    std::mutex mtx_;
    std::vector<int> resource_;
}

Обратите внимание, что вам нужно будет использоватьstd::unique_lockдля этого какstd::lock_guardне может быть построен разблокирован

По сути, у вас есть две разные функции:

      std::lock_guard<std::mutex> lock(mtx_);
resource_.push_back(k);
// code for task with resource_ ...
      resource_.push_back(k);
// code for task with resource_ ...

Таким образом, вы можете упаковать общие вещи в другую функцию:

      private:
    void do_something_unlocked(int k) {
        resource_.push_back(k);
        // code for task with resource_ ...
    }
public:
    void do_something(int k) {
        if constexpr (std::is_same_v<T, NeedMutexType>) {
            std::lock_guard lock(mtx_);
            do_something_unlocked(k);
        } else {
            do_something_unlocked(k);
        }
    }

Общее исправлениеif constexprсоздание собственной области видимости означает помещение ее в лямбду:

      void do_something(int k) {
    auto maybe_lock = [&]{
        if constexpr (std::is_same_v<T, NeedMutexType>) {
            return std::lock_guard{mtx_};
        } else {
            return 0;  // Dummy value; no lock needed
        }
    }();
    resource_.push_back(k);
    // code for task with resource_ ...
}

Это едва ли требует меньше ввода, чем вспомогательная функция, но может быть проще в использовании (особенно, если вы используете ее несколько раз и создаете функцию-членauto maybe_lock())

Другие вопросы по тегам