Как я могу предоставить доступ к этому буферу с помощью CSingleLock?

У меня есть эти два метода для потокового доступа к объекту CMyBuffer:

Заголовок:

class CSomeClass
{
//...
public:
    CMyBuffer & LockBuffer();
    void ReleaseBuffer();

private:
    CMyBuffer m_buffer;
    CCriticalSection m_bufferLock;
//...
}

Выполнение:

CMyBuffer & CSomeClass::LockBuffer()
{
    m_bufferLock.Lock();
    return m_buffer;
}

void CSomeClass::ReleaseBuffer()
{
    m_bufferLock.Unlock();
}

Использование:

void someFunction(CSomeClass & sc)
{
    CMyBuffer & buffer = sc.LockBuffer();
    // access buffer
    sc.ReleaseBuffer();
}
  • Что мне нравится в этом, так это то, что пользователь должен сделать только один вызов функции и может получить доступ к буферу только после его блокировки.
  • Что мне не нравится, так это то, что пользователь должен освобождать явно.

Обновление. На эти дополнительные недостатки указали Ник Мейер и Мартин Йорк:

  • Пользователь может снять блокировку и затем использовать буфер.
  • Если перед снятием блокировки возникает исключение, буфер остается заблокированным.

Я хотел бы сделать это с объектом CSingleLock (или чем-то подобным), который разблокирует буфер, когда объект выходит за пределы области видимости.

Как это можно было сделать?


person foraidt    schedule 10.08.2009    source источник


Ответы (3)


Используйте объект, представляющий буфер.
Когда этот объект инициализируется, получите блокировку, а когда он будет уничтожен, снимите блокировку.
Добавьте оператор приведения, чтобы его можно было использовать вместо буфера в любом вызове функции:

#include <iostream>

// Added to just get it to compile
struct CMyBuffer
{    void doStuff() {std::cout << "Stuff\n";}};
struct CCriticalSection
{
        void Lock()     {}
        void Unlock()   {}
};          

class CSomeClass
{
    private:
        CMyBuffer m_buffer;
        CCriticalSection m_bufferLock;

        // Note the friendship.
        friend class CSomeClassBufRef;
};

// The interesting class.
class CSomeClassBufRef
{
    public:
        CSomeClassBufRef(CSomeClass& parent)
            :m_owned(parent)
        {
           // Lock on construction
            m_owned.m_bufferLock.Lock();
        }
        ~CSomeClassBufRef()
        {
            // Unlock on destruction
            m_owned.m_bufferLock.Unlock();
        }
        operator CMyBuffer&()
        {
            // When this object needs to be used as a CMyBuffer cast it.
            return m_owned.m_buffer;
        }
    private:
        CSomeClass&     m_owned;
}; 

void doStuff(CMyBuffer& buf)
{           
    buf.doStuff();
}
int main()
{
    CSomeClass          s;

    // Get a reference to the buffer and auto lock.
    CSomeClassBufRef    b(s);

    // This call auto casts into CMyBuffer
    doStuff(b);

    // But you can explicitly cast into CMyBuffer if you need.
    static_cast<CMyBuffer&>(b).doStuff();
}
person Martin York    schedule 10.08.2009
comment
Что вы думаете о перегрузке operator-›()? Таким образом можно было бы сказать b-›doStuff() вместо static_cast. - person foraidt; 12.08.2009
comment
Приложение: я думаю, что static_cast был сделан настолько уродливым, чтобы сделать его привлекательным и не таким уж тривиальным для приведения чего-либо. Но в данном случае это действительно должно быть сделано именно так, поэтому нет необходимости усложнять это приведение, чем это необходимо для пользователя. - person foraidt; 12.08.2009
comment
В настоящее время я пробую это и заметил, что тогда operator*() также должен быть перегружен. - person foraidt; 12.08.2009
comment
Лично я бы не определил оператор*() или оператор-›(). Я бы использовал оператор приведения или, возможно, метод get() (в CSomeClassBufRef) - person Martin York; 12.08.2009

Один из способов сделать это — использовать RAII:

class CMyBuffer
{
public:
    void Lock()
    {
        m_bufferLock.Lock();
    }

    void Unlock()
    {
        m_bufferLock.Unlock();
    }

private:
    CCriticalSection m_bufferLock;

};

class LockedBuffer
{
public:
    LockedBuffer(CMyBuffer& myBuffer) : m_myBuffer(myBuffer)
    {
        m_myBuffer.Lock();
    }

    ~LockedBuffer()
    {

        m_myBuffer.Unlock();
    }

    CMyBuffer& getBuffer() 
    {
        return m_myBuffer;
    }

private:
    CMyBuffer& m_myBuffer;

};

class CSomeClass
{
    //...
public:
    LockedBuffer getBuffer();

private:
    CMyBuffer m_buffer;

};


LockedBuffer CSomeClass::getBuffer()
{
    return LockedBuffer(m_buffer);
}

void someFunction(CSomeClass & sc)
{
    LockedBuffer lb = sc.getBuffer();
    CMyBuffer& buffer = lb.getBuffer();
    //Use the buffer, when lb object goes out of scope buffer lock is released
}
person Naveen    schedule 10.08.2009
comment
В этом случае пользователь может сохранить возвращенный CMyBuffer & и продолжать использовать его даже после уничтожения объекта LockedBuffer. - person Nick Meyer; 10.08.2009
comment
@Nick: Как и исходный код. По крайней мере, эта версия защищает от проблем с исключениями. - person Martin York; 10.08.2009

ИМХО, если ваша цель - запретить пользователю доступ к буферу только тогда, когда он заблокирован, вы ведете сложную битву. Подумайте, делает ли пользователь:

void someFunction(CSomeClass & sc)
{
   CMyBuffer & buffer = sc.LockBuffer();
   sc.ReleaseBuffer();
   buffer.someMutatingMethod(); // Whoops, accessed while unlocked!
}

Чтобы разрешить пользователю доступ к буферу, вы должны вернуть ссылку на буфер, которую они всегда могут совершить по ошибке, удерживая до тех пор, пока блокировка не будет снята.

Тем не менее, вы можете сделать что-то вроде этого:

class CMyBuffer
{
   private:
      void mutateMe();
      CCriticalSection m_CritSec;

   friend class CMySynchronizedBuffer;
};

class CMySynchronizedBuffer
{
   private:
      CMyBuffer & m_Buffer;
      CSingleLock m_Lock

   public:
      CMySynchronizedBuffer (CMyBuffer & buffer)
         : m_Buffer (buffer)
         , m_Lock (&m_Buffer.m_CritSec, TRUE)
      {
      }

      void mutateMe()
      {
         m_Buffer.mutateMe();
      }
};

Используйте как:

{
   CMyBuffer buffer;  // Or declared elsewhere
   // buffer.mutateMe();  (Can't do this)
   CMySyncrhonizedBuffer synchBuffer (buffer); // Wrap it & lock it
   synchBuffer.mutateMe();  // Now protected by critical section
} // synchBuffer and its CSingleLock member are destroyed and the CS released

Это заставляет пользователя обернуть объект CMyBuffer в объект CMySynchronizedBuffer, чтобы получить доступ к любому из его изменяющих методов. Поскольку на самом деле он не предоставляет доступ к базовому объекту CMyBuffer, возвращая ссылку, он не должен давать пользователю ничего, за что можно было бы зацепиться и измениться после снятия блокировки.

person Nick Meyer    schedule 10.08.2009