是否可以重新分配 std::unique_ptr,使其旧值在构造新值之前*被销毁?

Can a std::unique_ptr be reassigned such that its old value is destroyed *before* the new one is constructed?

我有兴趣将旧的个人项目更新为现代 C++。我很欣赏 RAII 如何简化清理:不是创建一个 new 对象并记住在函数中的每个 return 点之前 delete 它,只是 make_unique 它将被适当地销毁.但是在比较生成的程序集时我有点挑剔。

假设有一个 class 方法用新值替换其中一个 unique_ptr 成员:

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

这将创建一个 new int,将其分配给 m_foo,然后 delete m_foo 的旧值。但这与旧行为不太一样,旧行为可以 delete 旧值,然后创建一个 new 并将其分配给 m_foo:

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

Compiler Explorer 开始,gcc、clang 和 MSVC 都为旧方法生成的代码少于新方法。我理解为什么会这样,因为这是所有表达式的求值顺序; p = q; 必须先构造 q。当然 m_foodeletenew 行之间有一个无效值。但是有没有办法让 unique_ptr 破坏它的旧值,然后在一个表达式中创建一个新的值?一个“replace_unique”?似乎在处理大对象时会有所帮助,因此它们中的两个不会不必要地共存。

编辑: 明确地说,我在这里只是使用 int 作为一个简单的例子。我的实际用例是 class 个我自己的或来自图书馆的。并且 m_foo.reset(new int(x * 3 + 5)); 与旧的做事方式相比自然有同样的问题,因为它也必须在分配之前构造 new int 并且 deleting 前一个.

您可以在需要时使用unique_ptr::reset()确定性地销毁当前保存的对象。用 nullptr 指针更新 unique_ptr,然后用新的对象指针更新它,例如:

// 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);
}

我接受了在 m_foo = std::make_unique<int>(...) 之前调用 m_foo.reset() 的答案,因为这是解决所述问题的最简单方法:在构造新值之前先销毁原始值。但是,这确实会导致额外的 delete 调用,这不是必需的,并且不如使用原始指针的“前现代”方式最佳。

这种方法,至少在Compiler Explorer-Ofast中,避免了第二次delete调用,并且与旧方法相比只多了一个mov指令(在分配新构造的值之前显式设置 m_foonullptr):

// 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
}

作为可重复使用的模板函数:

template<typename T, typename... Ps>
void remake_unique(std::unique_ptr<T> &p, Ps... args) {
    p = nullptr;
    auto new_p = std::make_unique<T>(args...);
    p.swap(new_p);
    new_p.release();
}

// std::unique_ptr<int> MyClass::m_foo;
void refresh_smart(int x) {
    remake_unique(m_foo, x * 3 + 5);
}