Search code examples
c++c++11language-lawyershared-ptrmove-semantics

Is it safe to call a method on a smart pointer that is moved-from in the arguments?


Is this code safe? Does the answer change if the method accepts by value or by rvalue ref? Does it change for unique_ptr?

struct foo
{
    void bar(std::shared_ptr<foo> p) // or std::shared_ptr<foo>&&
    {
        // the object will be deleted at the end of the call unless p is
        // moved/copied elsewhere at some point
    }
};

int main()
{
    auto p = std::make_shared<foo>();
    p->bar(std::move(p));
    return 0;
}

The main question is specifically on this line:

p->bar(std::move(p));

Is this guaranteed to always capture the current value of p.operator->() before constructing the argument? Or could this happen after moving-from p?

(Note: I am confident that this is safe for std::shared_ptr<foo>&& argument, as then the actual move-construction doesn't happen until inside the method body, if at all. But when passed by value, can the argument construction [which includes the move-construction] occur prior to calling p.operator->() or is it always strictly afterwards?)


Solution

  • If you are using std::shared_ptr<foo>&& as parameter type (or any reference type), then there is no problem, because the std::move call doesn't actually modify the object in any way. It wouldn't matter whether it is executed first.

    Before C++17, the by-value variant is not safe. There is no sequencing rule that guarantees that the expression naming the function is evaluated before the initialization of the function parameters. So the move construction of the function parameter could happen before p->bar is evaluated.

    Since C++17 the value computation and all side-effects of the postfix-expression naming the function are sequenced before the value computation and side-effects of all expressions in the call arguments, which implies that the constructor of the function parameter is sequenced after the evaluation of p->bar. See [expr.call]/5. Therefore the potentially problematic operator-> call on p happens first, before the move construction, making the code safe.

    The same statements hold if you replace std::shared_ptr with std::unique_ptr.