Как мога да осигуря достъп до този буфер с 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
Лично аз не бих дефинирал operator*() или operator-›(). Бих използвал оператора за предаване или потенциално метод 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

IMHO, ако целта ви е да попречите на потребителя да има достъп до буфера само когато е заключен, вие водите трудна битка. Помислете дали потребителят прави:

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