読者です 読者をやめる 読者になる 読者になる

std::unique_lockはミューテックスインタフェースを持つ

C++

std::unique_lockクラスはstd::lock_guardクラスと違って多くの機能を持っています。
ひとつは昨日書いたムーブですが、もうひとつは「ミューテックスインタフェース」を持っているということです。


std::unique_lockとした場合、Mutexがlock()/unlock()のインタフェースを持っているクラスであれば、std::unique_lockクラスもまたlock()/unlock()インタフェースを使用できます。
Mutexが、std::timed_mutexやstd::recursive_timed_mutexのように、try_lock_for()/try_lock_until()のインタフェースを持っているクラスであれば、std::unique_lockクラスもそのインタフェースを使用できます。


そのため、std::unique_lockクラス自体を、std::lock_guardクラスやstd::unique_lockクラスのテンプレート引数として指定できます。やってみましょう。

#include <iostream>
#include <thread>
#include <mutex>

class X {
    int value_ = 0;
    mutable std::mutex mtx_;
public:
    void add_value()
    {
        std::unique_lock<std::mutex> outer_lock(mtx_, std::defer_lock); // すぐにはロックしない
        std::lock_guard<std::unique_lock<std::mutex>> inner_lock(outer_lock); // ここでロックする
        ++value_;
    }
    // inner_lockのデストラクタでunlock()される
    // outer_lock(std::unique_lock)はunlock()しない

    int get() const
    {
        std::lock_guard<std::mutex> lk(mtx_);
        return value_;
    }
};

int main()
{
    X x;

    std::thread t1([&] { x.add_value(); });
    std::thread t2([&] { x.add_value(); });

    t1.join();
    t2.join();

    std::cout << x.get() << std::endl;
}

std::unique_lockクラスはアンロック状態であればデストラクタでunlock()を呼ばないので、こういうネストができるのでした。


参照:
std::unique_lock - cpprefjp
std::lock_guard - cpprefjp