Как создать интеллектуальный указатель, который блокирует и разблокирует мьютексы?

У меня есть поточный класс, из которого я хотел бы иногда получать указатель переменной экземпляра. Я хотел бы, чтобы этот доступ был защищен мьютексом, чтобы поток был заблокирован от доступа к этому ресурсу, пока клиент не завершит свой указатель.

Мой первоначальный подход к этому - вернуть пару объектов: один указатель на ресурс и один shared_ptr на объект блокировки на мьютексе. Этот shared_ptr содержит единственную ссылку на объект блокировки, поэтому мьютекс должен быть разблокирован, когда он выходит из области видимости. Что-то вроде этого:

void A::getResource()
{
    Lock* lock = new Lock(&mMutex);
    return pair<Resource*, shared_ptr<Lock> >(
        &mResource, 
        shared_ptr<Lock>(lock));
}

Это решение является менее идеальным, поскольку требует, чтобы клиент держался за всю пару объектов. Такое поведение нарушает безопасность потока:

Resource* r = a.getResource().first;

Кроме того, моя собственная реализация этого является взаимоблокировкой, и у меня возникают трудности в определении того, почему, возможно, что-то не так.

То, что я хотел бы иметь, это shared_ptr, который содержит блокировку как переменную экземпляра, привязывая ее к средствам доступа к ресурсу. Это похоже на то, что должно иметь установленный образец дизайна, но, проведя некоторые исследования, я с удивлением обнаружил, что его трудно встретить.

Мои вопросы:

  • Существует ли общая реализация этого шаблона?
  • Есть ли проблемы с помещением мьютекса внутри shared_ptr, который я пропускаю, чтобы предотвратить распространение этого шаблона?
  • Есть ли веская причина не реализовывать мой собственный класс shared_ptr для реализации этого шаблона?

(NB Я работаю над кодовой базой, которая использует Qt, но, к сожалению, не может использовать boost в этом случае. Однако ответы, связанные с повышением, по-прежнему представляют общий интерес.)

Ответ 1

Я не уверен, есть ли какие-либо стандартные реализации, но поскольку мне нравится переустанавливать материал без причины, вот версия, которая должна работать (при условии, что вы не хотите копировать такие указатели):

template<class T>
class locking_ptr
{
public:
  locking_ptr(T* ptr, mutex* lock)
    : m_ptr(ptr)
    , m_mutex(lock)
  {
    m_mutex->lock();
  }
  ~locking_ptr()
  {
    if (m_mutex)
      m_mutex->unlock();
  }
  locking_ptr(locking_ptr<T>&& ptr)
    : m_ptr(ptr.m_ptr)
    , m_mutex(ptr.m_mutex)
  {
    ptr.m_ptr = nullptr;
    ptr.m_mutex = nullptr;
  }

  T* operator ->()
  {
    return m_ptr;
  }
  T const* operator ->() const
  {
    return m_ptr;
  }
private:
  // disallow copy/assignment
  locking_ptr(locking_ptr<T> const& ptr)
  {
  }
  locking_ptr& operator = (locking_ptr<T> const& ptr)
  {
    return *this;
  }
  T* m_ptr;
  mutex* m_mutex; // whatever implementation you use
};

Ответ 2

Вы описываете вариант EXECUTE AROUND POINTER, описанный Кевлином Хенни в Выполнение последовательности последовательности.

У меня есть прототипная реализация на exec_around.h, но я не могу гарантировать, что он работает правильно во всех случаях, поскольку это незавершенная работа, Он включает в себя функцию mutex_around, которая создает объект и обертывает его интеллектуальным указателем, который блокирует и разблокирует мьютекс при доступе.