Проблемы с 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()
)