为什么std :: lock_guard/std :: unique_lock没有使用类型擦除?

Ami*_*ory 21 c++ multithreading type-erasure

为什么std::lock_guardstd::unique_lock必要规定锁定类型作为模板参数?

考虑以下替代方案.首先,在detail命名空间中,有类型擦除类(非模板抽象基类和模板派生类):

#include <type_traits>
#include <mutex>
#include <chrono>
#include <iostream>

namespace detail {

    struct locker_unlocker_base {
        virtual void lock() = 0;
        virtual void unlock() = 0;
    };

    template<class Mutex>
    struct locker_unlocker : public locker_unlocker_base {
        locker_unlocker(Mutex &m) : m_m{&m} {}
        virtual void lock() { m_m->lock(); }
        virtual void unlock() { m_m->unlock(); }
        Mutex *m_m;
    };
}
Run Code Online (Sandbox Code Playgroud)

现在te_lock_guard,类型擦除锁定保护,简单地放置 - 在构造时消息正确类型的对象(没有动态内存分配):

class te_lock_guard {
public:
    template<class Mutex>
    te_lock_guard(Mutex &m) {
        new (&m_buf) detail::locker_unlocker<Mutex>(m);
        reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->lock();
    }
    ~te_lock_guard() {
        reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->unlock();
    }

private:
    std::aligned_storage<sizeof(detail::locker_unlocker<std::mutex>), alignof(detail::locker_unlocker<std::mutex>)>::type m_buf;
};
Run Code Online (Sandbox Code Playgroud)

我已经检查了性能与标准库的类:

int main() {
    constexpr std::size_t num{999999};
    {
        std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
        for(size_t i = 0; i < num; ++i) {
            std::mutex m;
            te_lock_guard l(m);
        }
        std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now();
        std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl;
    }
    {
        std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
        for(size_t i = 0; i < num; ++i) {
            std::mutex m;
            std::unique_lock<std::mutex> l(m);
        }
        std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now();
        std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用g ++ -O3,没有统计上显着的性能损失.

Vit*_*meo 28

因为这使得实现变得复杂并没有任何显着的好处,并且隐藏了这样的事实,std::lock_guard并且std::unique_lock知道他们在编译时保护的锁的类型.

您的解决方案是一种解决方法,因为在构造期间不会发生类模板参数推断 - 这在即将出台的标准中得到了解决.

由于构造函数(P0091R3)提议的模板参数推断,需要指定锁类型是令人烦恼的样板,将在C++ 17中解决(不仅仅用于锁定保护).

该提议(已被接受)允许从构造函数推导出模板参数,不需要make_xxx(...)辅助函数或显式指定编译器应该能够推导出的类型名:

// Valid C++17
for(size_t i = 0; i < num; ++i) {
    std::mutex m;
    std::unique_lock l(m);
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*ges 10

滚动C++ 17 ......与此同时,不需要类型擦除.模板函数参数推导允许我们一个简单的帮助:

template<class Mutex>
auto make_lock(Mutex& m)
{
    return std::unique_lock<Mutex>(m);
}

...

std::mutex m;
std::recursive_mutex m2;

auto lock = make_lock(m);
auto lock2 = make_lock(m2);
Run Code Online (Sandbox Code Playgroud)