qtconcurrencyqt4mutexqreadwritelock

QReadWriteLock in accessors


I have a QList of MyClass. Appending and deleting items from list are rare and cotrolled by common list mutex. MyClass contains several substructures and personal QReadWriteLock:

MyClass{
private:
   Substructure substructure;
   QReadWriteLock rwlElem;
}

I put lockers in accesors like that:

Substructure MyClass::getSub(){
  QReadLocker lock(&rwlElem);
  return substructure;
}

I expect that copy of substructure will be safely returned. Same things I made in setters. Is this good practice to use lockers like that? What happens first: copy construction of the substructure or destruction of the locker?


Solution

  • I'll start by answering your last question:

    What happens first: copy construction of the substructure or destruction of the locker?

    TL;DR: copy construction happens before destruction of all local variables.

    This has been asked several times here on stackoverflow, see here, here and here.

    More precise wording has been applied to the C++14 standard by CWG 1885:

    N4606 §6.6.3 [stmt.return]/3

    The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables (6.6) of the block enclosing the return statement.


    I have a QList of MyClass. Appending and deleting items from list are rare and cotrolled by common list mutex. MyClass contains several substructures and personal QReadWriteLock

    Having two mutexes to lock can make you prone to deadlocks. However you can avoid that by following one of these two guidelines:


    Same things I made in setters. Is this good practice to use lockers like that?

    Assuming you used QWriteLocker in your setters, this should be okay (in the sense that it does not cause undefined behavior). But you have to pay attention to what your interface is really saying. For example when you are doing something like this to an instance mc of type MyClass:

    Substructure s = mc.getSub();
    if(s == anotherSubstructure)
        mc.doSomething();
    

    By the time the thread executing the above code gets to compare s with anotherSubstructure, another thread might change the actual substructure inside mc, and this change isn't visible to the former thread (since it gets its own copy of the substructure).

    The point is, when you do Substructure s = mc.getSub();, the only thing you get to know is that mc's substructure was equal to s at some point in time, and this value might change as soon as you released the lock in your getSub function.

    In my above example, doSomething() cannot assume that the value of mc's substructure is still equal to anotherSubstructure. You may need to group operations that need to be atomic in their own functions (with locks properly assuring that the values don't change in a case like the above).