如何使用另一个类作为类模板专用化

How to use another class as a class template specialization

本文关键字:专用 何使用 另一个      更新时间:2023-10-16

我有一个混合锁类,它在返回到std::mutex上的阻塞之前,对一个锁进行旋转尝试(编译时固定)多次旋转,直到该锁可用。

简化:

#include <mutex>
template<unsigned SPIN_LIMIT>
class hybrid_lock {
public:
    void lock(){
        for(unsigned i(0);i<SPIN_LIMIT;++i){
            if(this->mMutex.try_lock()){
                return;        
            }
        } 
        this->mMutex.lock();
    }
    void unlock(){
        this->mMutex.unlock();
    }
private:
    std::mutex mMutex;
};

SPIN_LIMIT==0的特殊情况下,这又回到了"普通"std::mutex(即没有可见的自旋)。

所以我把它专门用于:

template<>
class hybrid_lock<0> : public std::mutex {};

它工作得很好,但这是将类模板专门化为另一个(预先存在的)模板的批准方式吗?

注意:我回答的是实际问题,而不是标题中的问题。

好吧,现在hybird_lock<0>hybird_lock<1>有很大的不同,一个来源于std::mutex,另一个包含/包装它。这改变了hybird_lock的整个组成及其背后的含义。也就是说,它们在语义上不相同。这可能会导致一些意想不到的后果——hybird_lock<0>会继承很多其他情况下不会有的东西。

如果这是唯一的区别的话,我根本不会去关注专业化。请记住,零情况在编译时是已知的,当然,整个循环将完全优化。

如果还有其他重要的(或实际的)优化,我会选择这样的优化:

template<>
class hybrid_lock<0> {
public:
    void lock(){
      this->mMutex.lock();
    }
    void unlock(){
      this->mMutex.unlock();
    }
private:
    std::mutex mMutex;
};

这种实现使0成为一种特殊情况,而不是几乎完全不同的情况。

没有"官方"方法可以做到这一点,但这里有一个好方法-对于模板,最好将主模板类分解为较小的"action"或"function"模板类。通过这种方式,你可以对专业化进行更多的控制和粒度控制,这意味着你只需要在一个地方维护主要逻辑:

#include <iostream>
#include <mutex>
// general form of the spin_locker
template<unsigned SPIN_LIMIT, class Mutex>
struct spinner
{
    static void lock(Mutex& m) {
        for (unsigned i = 0 ; i < SPIN_LIMIT ; ++i)
            if (m.try_lock())
                return;
        m.lock();
    }
};
// optmised partial specialisation for zero spins
template<class Mutex>
struct spinner<0, Mutex>
{
    static void lock(Mutex& m) {
        m.lock();
    }
};
template<unsigned SPIN_LIMIT, class Mutex = std::mutex>
class hybrid_lock {
    using spinner_type = spinner<SPIN_LIMIT, Mutex>;
public:
    void lock(){
        spinner_type::lock(mMutex);
    }
    void unlock(){
        mMutex.unlock();
    }
    std::unique_lock<Mutex> make_lock() {
        return std::unique_lock<Mutex>(mMutex);
    }
private:
    Mutex mMutex;
};
// since only the 'spinner' functor object needs specialising there is now no need to specialise the main logic
using namespace std;
auto main() -> int
{
    hybrid_lock<100> m1;
    hybrid_lock<0> m2;
    hybrid_lock<100, std::recursive_mutex> m3;
    hybrid_lock<0, std::recursive_mutex> m4;
    auto l1 = m1.make_lock();
    auto l2 = m2.make_lock();
    auto l3 = m3.make_lock();
    auto l4 = m4.make_lock();
    return 0;
}

Richard Hodges的答案很好,但您可以简单地重载方法lock:

#include <iostream>
#include <type_traits>
#include <mutex>
template<class Mutex = std::mutex>
struct hybrid_lock {
    template<int N>
    void lock(std::integral_constant<int, N> val){
        for (unsigned i = 0 ; i < val() ; ++i)
            if (mMutex.try_lock())
                return;
        mMutex.lock();
    }
    void lock(std::integral_constant<int, 0>){
        mMutex.lock();
    }
    void unlock(){
        mMutex.unlock();
    }
    std::unique_lock<Mutex> make_lock() {
        return std::unique_lock<Mutex>(mMutex);
    }
private:
    Mutex mMutex;
};
template <int N>
constexpr
std::integral_constant<int, N> IC;
int main() {
    hybrid_lock<> m1;
    hybrid_lock<> m2;
    m1.lock(IC<0>);
    m2.lock(IC<100>);
    return 0;
}

IC是一个可变模板,从c++14开始,如果你的编译器不支持它,你可以使用类型别名类型别名:

template<int N>
using IC = std::integral_constant<int, N>;

并像这个一样使用它

m.lock(IC<0>{});