Home > Back-end >  Can a std::unique_ptr be reassigned such that its old value is destroyed *before* the new one is con
Can a std::unique_ptr be reassigned such that its old value is destroyed *before* the new one is con

Time:09-22

I'm interested in updating an old personal project to modern C . I appreciate how RAII simplifies cleanup: instead of making a new object and remembering to delete it before every return point in a function, just make_unique and it will be destroyed appropriately. But I have one nitpick when comparing the generated assembly.

Say there's a class method that replaces one of its unique_ptr members with a new value:

// std::unique_ptr<int> MyClass::m_foo;
void MyClass::refresh_foo(int x) {
    m_foo = std::make_unique<int>(x * 3   5);
}

This will create a new int, assign it to m_foo, and then delete the old value of m_foo. But that's not quite the same as the old behavior, which could delete the old value, then create a new one and assign it to m_foo:

// int *MyClass::m_foo;
void MyClass::refresh_foo(int x) {
    delete m_foo;
    m_foo = new int(x * 3   5);
}

From the Compiler Explorer, gcc, clang, and MSVC all generate less code for the old way than the new one. I understand why this is the case, since that's the order in which all expressions are evaluated; p = q; has to construct q first. And of course m_foo had an invalid value in between the delete and new lines. But is there a way I'm missing to have unique_ptr destroy its old value and then create a new one in one expression? A "replace_unique"? Seems like it would be helpful when dealing with large objects so two of them don't needlessly coexist.

Edit: To be clear, I'm just using int as a trivial example here. My actual use cases are with classes of my own or from a library. And m_foo.reset(new int(x * 3 5)); naturally has the same problem compared to the old way of doing things, since it too has to construct the new int before assigning it and deleting the previous one.

CodePudding user response:

You can use unique_ptr::reset() to deterministically destroy the currently held object when you want. Update the unique_ptr with a nullptr pointer before then updating it with a new object pointer, eg:

// std::unique_ptr<int> MyClass::m_foo;
void MyClass::refresh_foo(int x) {
    m_foo.reset(); // <- int is destroyed here
    m_foo = std::make_unique<int>(x * 3   5);
}

CodePudding user response:

I accepted the answer which calls m_foo.reset() before m_foo = std::make_unique<int>(...) because it's the simplest way to solve the problem as stated: destroying the original value before constructing the new one. However, that does result in an extra delete call which shouldn't be necessary and is less optimal than the "pre-modern" way of using raw pointers.

This method, at least in Compiler Explorer with -Ofast, avoids the second delete call, and only has one extra mov instruction compared to the old way (explicitly setting m_foo to nullptr before assigning its newly constructed value):

// std::unique_ptr<int> MyClass::m_foo;
void refresh_smart(int x) {
    m_foo = nullptr;
    auto new_foo = std::make_unique<int>(x * 3   5);
    m_foo.swap(new_foo);
    new_foo.release(); // no memory leak since it's null
}
  • Related