Search code examples
c++c++11stackshared-ptrmove-semantics

Returning shared_ptr and exception safety


I am reading "C++ Concurrency in action" book and trying to comprehend exception safety in thread-safe data structures (for example stack). Author states that to avoid race condition, pop should do both operations - pop and return item from the stack, however:

If the pop() function was defined to return the value popped, as well as remove it from the stack, you have a potential problem: the value being popped is returned to the caller only after the stack has been modified, but the process of copying the data to return to the caller might throw an exception.

Here is proposed solution:

std::shared_ptr<T> pop()
{
    std::lock_guard<std::mutex> lock(m);
    if(data.empty()) throw runtime_error("empty");
    std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
    data.pop();
    return res;
}

In this case we got invocation of copy constructor on make_shared line. If copy constructor throws an exception, stack is not yet modified and we are good.

However I don't see how it differs much from this snippet:

T pop2() {
    std::lock_guard<std::mutex> lock(m);
    if(data.empty()) throw runtime_error("empty");
    auto res = data.top();
    data.pop();
    return res;
}

Here we have copy constructor invocation at data.top() line and move constructor on return. Again, if copy constructor throws an exception we are good since stack is not yet modified. Move contstructor is not supposed to throw exceptions.

Am I missing something? What is the benefit of returning shared_ptr comparison to returning (movable) T ?


Solution

  • If T is not movable, your code might end up doing multiple copy-constructions. Using a shared_ptr to allocate the copy on the heap might be more efficient in this case.

    And even if T is movable, your code might still do multiple (potentially costly) move-constructions. Move-constructing and/or copy-constructing a shared_ptr is known to be relatively cheap compared to move- or copy-constructing any possible type T.

    Of course your mileage may vary depending on the exact types, your compiler and on your operating environment and hardware.