Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a member unique_ptr<> left non-null after std::move to a throwing function? [duplicate]

Tags:

When I move a unique_ptr<> into a function that throws, the moved-from pointer is left non-null. Is this the normal behavior?

Here's two test programs that show the behavior. I can observe the unique pointer in the destructor of the class:

#include <memory>
#include <iostream>

void f(std::unique_ptr<int> &&) { throw "fail"; }
struct except_move_tester
{
    std::unique_ptr<int> x;
    except_move_tester()
      : x(std::make_unique<int>(0))
    {}
    ~except_move_tester() { std::cout << "x at destructor: " << x.get() << std::endl; }
    void g()
    {
        std::cout << "x at g: " << x.get() << std::endl;
        f(std::move(x));
    }
};

int main()
{
    try {
        except_move_tester t;
        t.g();
    } catch (...) {}
}

which when run gives the output:

x at g: 0x7f818b402ac0
x at destructor: 0x7f818b402ac0

If I modify the above listing as follows (just added a temporary at the site of the function call) I get the exception safe behavior I would normally expect:

#include <memory>
#include <iostream>

void f(std::unique_ptr<int> &&) { throw "fail"; }
struct except_move_tester
{
    std::unique_ptr<int> x;
    except_move_tester()
      : x(std::make_unique<int>(0))
    {}
    ~except_move_tester() { std::cout << "x at destructor: " << x.get() << std::endl; }
    void g()
    {
        std::cout << "x at g: " << x.get() << std::endl;
        auto y = std::move(x);
        f(std::move(y));
    }
};

int main()
{
    try {
        except_move_tester t;
        t.g();
    } catch (...) {}
}

which when run gives the output:

x at g: 0x7f818b402ac0
x at destructor: 0x0

I've been moving unique_ptr's into functions under the assumption it was a sort of atomic exception safe operation, but this seems to indicate that an exception can leave a unique pointer in an unexpected state.

like image 784
Steve Cox Avatar asked Oct 02 '19 14:10

Steve Cox


1 Answers

std::move just converts the object to rvalue, but won't perform move operation.

(emphasis mine)

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.

In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.

Your 2nd snippet works because you moved x into y explicitly.

To fix the 1st snippet you can also perform the move operation explicitly, e.g.

void f(std::unique_ptr<int> && p) { 
    std::unique_ptr<int> t = std::move(p); // move-construct t from p
    throw "fail"; 
}

or just

void f(std::unique_ptr<int> p) { throw "fail"; }

For the latter, given f(std::move(x));, the parameter p is moved from the argument x.

like image 68
songyuanyao Avatar answered Oct 12 '22 23:10

songyuanyao