Search code examples
c++multithreadingreferencesmart-pointerslock-free

Disadvantages of using reference parameter to return value in lock-free pop()


I am currently reading Williams "C++ Concurrency in Action". Now I stopped on the topic dedicated to the implementation of lock-free pop().

Lock-free pop:

void pop(T& result)
{
 node* old_head = head.load();
 while(!head.compare_exchange_weak(old_head,old_head->next));
 result=old_head->data;
}

Here is a quote from the discussion of this code:

The second problem is an exception-safety issue. When we first introduced the thread-safe stack back in chapter 3, you saw how just returning the object by value left you with an exception safety issue: if an exception is thrown when copying the return value, the value is lost. In that case, passing in a reference to the result was an acceptable solution because you could ensure that the stack was left unchanged if an exception was thrown. Unfortunately, here you don’t have that luxury; you can only safely copy the data once you know you’re the only thread returning the node, which means the node has already been removed from the queue. Consequently, passing in the target for the return value by reference is no longer an advantage: you might as well just return by value. If you want to return the value safely, you have to use the other option from chapter 3: return a (smart) pointer to the data value

I do not understand how the use of reference can lead to problems with exceptions. I see word 'copy' here, but it can't be used with references, 'initialization', 'assignment' but surely not 'copy'. So, I do not understand everyting that is written after this 'copy'.

While googling, I found a Williams explanation: https://forums.manning.com/posts/list/30887.page

but this does not clarify the question for me, because he uses 'copy' with references again:

"Normal" stack:

void pop(T& value)
{
 std::lock_guard<std::mutex> lock(m);
 if(data.empty()) throw empty_stack();
 value=data.top();
 data.pop();
}

With a "normal" stack, the advantage of a reference parameter for the result is that you can copy the result before you remove the node from the stack, and if the copy to the result throws then the node is still there for another call to take.

Here is my question: Does the use of references in this case could lead to the generation of exceptions, and why I may as well just return by value?


Solution

  • When you do result=old_head->data; you copy old_head->data in-to result. You are right that in this case assignment operator is used rather than copy constructor. But that's not important, important thing is that some non trivial actions may be needed to copy data from old_head->data to result and that actions may trigger an exception.

    I think that Williams uses word "copy" because it is the most general world that can be used here. He also speaks about returning a value and in this case there may be invocation of copy constructor, move constructor, assignment operator and move assignment operator depending on the code. Instead of bloating text with a lot of verbs he just uses word "copy" because the only essential part is that we have data in one place and we need it in another.