基于模板的条件类成员

时间:2020-06-20 18:50:56

标签: c++ c++17

我希望有一个接收互斥量类型作为模板参数的类,以(有条件地)保证线程安全。如果互斥锁类型等于NoMutex(我的互斥锁为空实现),则我不想将此互斥锁成员添加到类中,这样可以节省空间。 我想做这样的事情:

class NoMutex{
// Same interface as std::mutex but empty implementation
// so optimization can remove unnecessary function calls
};

template <class Mutex>
class MyClass {
    public:
    // Actually I want to hide it somehow. I dont want it to occupy any space if Mutex == NoMutex
    some_template_trick<std::is_same_v<Mutex, NoMutex, void, Mutex> mutex;

    void someFunction(){
        std::unique_lock<Mutex> l(getMutex()); // I want to use std::unique_lock here
        // do some concurrent stuff

    }


    Mutex& getMutex(){
        if constexpr(std::is_same_v<Mutex, NoMutex>){
            //ok... return reference to temporary is wrong, but i'm hopping
            //that gcc will realize there is nothing to be done once all implementations
            //are empty.
            return NoMutex();
        }
        return mutex;
    }
};

我不想专门化类,也不想创建lock()unlock()方法,我希望它们由std :: unique_lock();来管理。 有可能吗?

谢谢!

2 个答案:

答案 0 :(得分:3)

可以使用空基类优化(EBO)来优化

空数据成员。也就是说,尽管无状态类的大小为非零(至少1个字节),但是如果用作基类,则不会消耗额外的内存:

template <typename Mutex>
struct CompressedMutex
{
    Mutex mutex;
    Mutex& getMutex() { return mutex; }
};

template <>
struct CompressedMutex<NoMutex> : NoMutex
{
    NoMutex& getMutex() { return *this; }
};

template <typename Mutex>
class MyClass : CompressedMutex<Mutex>
{
public: 
    void someFunction()
    {
        std::unique_lock<Mutex> l(this->getMutex());
    }
};

DEMO

请注意,访问作为相关名称的this->时需要CompressedMutex<Mutex>::getMutex()


但是,通常,人们希望在const限定的成员函数中也锁定互斥量。为此,可以在互斥量数据成员的定义中使用mutable关键字。在NoMutex情况下,需要使用const_cast<CompressedMutex&>(*this)或声明返回的static对象:

template <typename Mutex>
struct CompressedMutex
{
    mutable Mutex mutex;
    Mutex& getMutex() const { return mutex; }
};

template <>
struct CompressedMutex<NoMutex>
{
    static NoMutex mutex;
    NoMutex& getMutex() const { return mutex; }
};

此技术已在标准库中广泛使用,因此无状态分配器(包括std::allocator<T>)不占容器的总大小。这些对象通常存储在所谓的压缩对(例如boost::compressed_pair)或其变体中,有时与非空数据成员一起存储,以便封闭类的接口可以不变:

#include <boost/compressed_pair.hpp>

template <typename Mutex>
class MyClass
{
public: 
    void someFunction()
    {
        std::unique_lock<Mutex> l(data.second());
    }
private:
    boost::compressed_pair<SomeDataMemberType, Mutex> data;
};

答案 1 :(得分:0)

您可以使用包含一两个东西(未测试的代码)的结构包装器来执行此操作:

template <class T1, class T2> class Pair {
   Pair (const T1 &t1, const T2 &t2) : t1_(t1), t2_(t2) {}
   T1 & first () { return t1_; }
   T2 & second () { return t2_; }

   T1 t1_;
   T2 t2_;
};

template <class T1> class Pair<T1, NoMutex> {
   Pair (const T1 &t1) : t1_(t1) {}
   T1 & first () { return t1_; }
   NoMutex second () { return NoMutex(); }

   T1 t1_;
};