There is a class template Foo<T>
. And for some specific type, a function should use lock_guard
.
Here is the example code:
#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_;
};
The std::lock_guard
will destructed in the end of if constexpr scope. (If it's not true, please correct me.)
To handle this, I can copy the code for task with resource_
below into the if constexpr scope, or just use the raw std::mutex
such as mtx_.lock()
& mtx_.unlock()
instead.
Is there any some ways to handle this with std::lock_guard
? Thanks.
Perhaps std::conditional could come to the rescue here, if you need to do this kind of thing a lot.
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;
Here we've defined a do-nothing class template that's constructed the same way as std::lock_guard
. We then use that with std::conditional
to select either std::lock_guard
or FakeLockGuard
depending on the result of your type-check.
Now you can use it as follows:
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_;
};
You can easily verify this works by setting a breakpoint in the FakeLockGuard
constructor or making it output something.
That's how you can make it all work at compile-time. But I think as you alluded to already, you can simply construct a unique_lock
and then conditionally lock it. This has the benefit of being much clearer to whoever has to work with your code. In the end, it's up to whatever you think is most appropriate.