Правильный C++ способ реализации lock_guard для пользовательской библиотеки

О, мудрые сети

Между двумя коллегами мы зашли в тупик, и мы могли бы использовать вашу помощь для правильного решения C++. По сути, у нас есть набор служебных классов, два из которых - классы Mutex и SpinLock, оба имеют следующий сокращенный интерфейс:

class Mutex {
public:
    Mutex();
    ~Mutex();
    void Lock();
    void Unlock();
    // ...
};

Очевидно, что это похоже на концепцию BasicLockable, используемую std::lock_guard, но отличается от нее, поэтому мы хотим что-то похожее (предположим, что класс Mutex в этом примере неизменен; мы не можем добавить концепцию BasicLockable к нему). Также не все наши компиляторы для поддерживаемых платформ полностью поддерживают C++11, поэтому мы не можем просто использовать поддержку vanilla C++11.

Одной из идей является следующая реализация универсального класса защиты, который может быть унаследован для предоставления универсального класса защиты и наследоваться от него для создания класса блокировки-защиты:

template<class T, void (T::*EnterFn)(), void (T::*ExitFn)()>
class Guard
{
public: // copy constructor deleting omitted for brevity
    Guard( T *lock ) : m_lock(lock) { (m_lock->*EnterFn)(); }
    ~Guard();                       { (m_lock->*ExitFn)(); }
private:
    T *m_lock;
};

template<class T>
class LockGuard : public Guard<T, &T::Lock, &T::Unlock>
{
public:
    LockGuard(const T* lock) : Guard<T, &T::Lock, &T::Unlock>(lock) {}
};

Другая школа мысли состоит в том, чтобы просто реализовать простую защиту:

template<class T>
class LockGuard {
    T* m_lockable;
public:
    LockGuard(const T* lockable) : m_lockable(lockable) { lockable->Lock(); }
    ~LockGuard() { m_lockable->Unlock(); }
};

Какую реализацию вы бы выбрали и почему? Каков наиболее подходящий C++(03, 11, 14, 17) способ его реализации? Есть ли какая-то внутренняя ценность иметь общий класс Guard, как описано выше?

2 ответа

Я отвечу на вопрос в вашем заголовке, так как на него никто не ответил.

Согласно документам,lock_guard работает с любым типом, который "соответствует BasicLockableтребования ". BasicLockable требует только два метода,lock() а также unlock().

Чтобы сделать lock_guard работать с пользовательской библиотекой, вам нужно либо добавить lock() а также unlock() методы в класс мьютекса библиотеки или оберните его в другой класс, который имеет lock() а также unlock() методы.

Я не хотел бы использовать указатели метода.

Лично я бы хотел максимально приблизиться к стандартным инструментам C++11. Так что я бы написал адаптер.

template<class T>
struct lock_adapter {
  T* t = nullptr;
  void lock() { t->Lock(); }
  void unlock() { t->Unlock(); }
  lock_adapter( T& tin ):t(std::addressof(tin)) {}
  // default some stuff if you like
};

template<class T>
struct adapted_unique_lock:
  private lock_adapter<T>,
  std::unique_lock< lock_adapter<T> >
{
  template<class...Args>
  adapted_unique_lock(T& t, Args&&...):
    lock_adapter<T>(t),
    std::unique_lock< lock_adapter<T> >( *this, std::forward<Args>(args)... )
  {}
  adapted_unique_lock(adapted_unique_lock&&)=delete; // sadly
  friend void swap( adapted_unique_lock&, adapted_unique_lock& ) = delete; // ditto
};

сейчас adapted_unique_lock имеет ограниченный набор функций от std::unique_lock,

Он не может быть перемещен, так как unique_lock содержит указатель на this внутри его реализации и не переустанавливать его.

Обратите внимание, что богатство всего unique_lock набор конструктора доступен.

Функции, которые возвращают адаптированные уникальные блокировки, должны хранить свои возвращаемые значения во что-то вроде auto&& ссылки до конца области, и вы не можете вернуть их через цепочки до C++17.

Но любой код, использующий adapted_unique_lock можно поменять местами для использования unique_lock один раз T был изменен на поддержку .lock() а также .unlock(), Это перемещает вашу кодовую базу в сторону того, чтобы быть более стандартным в C++11, а не на заказ.

Вы должны использовать их: std::mutex, std::shared_lock, std::unique_lock, std::timed_mutex, std::shared_mutex, std::recursive_mutex, std::shared_timed_mutex, std::recursive_timed_mutex, std::lock_guard if на самом деле у вас есть компилятор C++11. В противном случае это более сложно, и тег C++11 по вопросу должен быть удален.

Вы не можете реализовать спин-блокировку или мьютекс с помощью средств C\C++, вам потребуется добавить ассемблер или встроенный код - что делает его непереносимым, если вы не реализуете его для каждой платформы - и со многими компиляторами x86-64 C++11 и новее Вы не можете сделать встроенный ассемблер.

Основная проблема, с которой вы столкнетесь, если будете использовать внутреннюю логику блокировки, состоит в том, что объекты за мьютексами или спин-блокировками не копируются. Как только вы копируете его или копируете защищаемую переменную, он перестает быть заблокированным. Объекты, которые реализуют мьютексную механику, также не копируются.

Другие вопросы по тегам