Условный член класса на основе шаблона - PullRequest
3 голосов
/ 20 июня 2020

Я хочу иметь класс, который принимает тип мьютекса в качестве параметра шаблона, чтобы быть (условно) потокобезопасным. Если тип мьютекса равен 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 ]

3 голосов
/ 20 июня 2020

Пустые элементы данных можно оптимизировать, используя оптимизацию пустого базового класса (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;
};
0 голосов
/ 20 июня 2020

Вы можете сделать это с помощью структуры-оболочки, содержащей одну или две вещи (непроверенный код):

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