c++ std::lock_guard< std::mutex>和if constexpr块的问题

46qrfjad  于 2023-05-02  发布在  其他
关注(0)|答案(3)|浏览(142)

有一个类模板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_;
};

std::lock_guard将在if constexpr作用域的末尾析构。(如果不是真的,请指正。)
为了处理这个问题,我可以将下面的resource_任务的代码复制到if constexpr作用域中,或者只使用原始的std::mutex,如mtx_.lock()mtx_.unlock()
有没有一些方法可以用std::lock_guard来处理这个问题?谢谢。

muk1a3rh

muk1a3rh1#

如果您经常需要做这种事情,也许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::lock_guard相同。然后,我们将其与std::conditional一起使用,根据类型检查的结果选择std::lock_guardFakeLockGuard
现在您可以按如下方式使用它:

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,然后有条件地锁定它。这样做的好处是,无论是谁,都可以更清楚地使用您的代码。最后,这取决于你认为最合适的方式。

sqserrrh

sqserrrh2#

只需在unlocked状态下构造锁,然后稍后锁定它:

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不能被构造为未锁定

b5lpy0ml

b5lpy0ml3#

你基本上有两个不同的功能:

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创建自己的作用域的一般修复方法是将其放在lambda中:

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

它只是比helper函数的类型少一点,但可能更容易使用(特别是如果您多次使用它并创建成员函数auto maybe_lock())。

相关问题