当重定向unique_ptr时会发生什么?

7

我理解unique_ptr是某个对象的唯一所有者,并且在其超出范围时释放该对象。但我不明白以下情况:

unique_ptr<int> p(new int(1));
p = unique_ptr<int>(new int(2));

如果将指针 p 重定向到另一个内存位置 new int(2)(因为 p 只能拥有其中之一),那么第一个对象 new int(1) 会发生什么?请注意,保留了 HTML 标签。

1
它已经丢失,永远消失了,但保证将被正确清理。如果类型不是 POD,则其析构函数将被调用(除非使用了某些自定义分配器)。 - Tas
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tracer
unique_ptr是像其他任何对象一样的对象,具有构造函数和析构函数。当调用p = unique_ptr时,将调用p的析构函数来清理内存。 - Tas
5
@Tas:不,p的赋值运算符是在清理内存。虽然会调用析构函数,但它不是 p 的析构函数,而是临时的 unique_ptr 移动后所调用的析构函数。请留意这点区别。 - Benjamin Lindley
事实上,这种安全和稳健性正是unique_ptr的全部目的。 - Lightness Races in Orbit
2个回答

11

unique_ptr是一种智能指针,当unique_ptr被销毁或重新赋值时,它所拥有的对象也会被销毁。例如:

#include <iostream>
#include <memory>
using namespace std;

struct T {
    T(int x) : x(x) {
        cout << "T(" << x << ")\n";
    }
    ~T() {
        cout << "~T(" << x << ")\n";
    }
    int x;
};

int main() {
    unique_ptr<T> p(new T(1));
    p = unique_ptr<T>(new T(2));
}

这将打印:

  • 在创建第一个对象时,打印 T(1)
  • 在创建第二个对象时,打印 T(2)
  • 当使用赋值运算符释放p的第一个对象时,打印~T(1)
  • 当使用析构函数释放p的第二个对象时,打印~T(2)

如果p的赋值运算符销毁了T(1),在这种情况下会发生什么?unique_ptr<T> p(new T(1)); unique_ptr<T> p2(new T(2)); p2 = move(p);谁销毁了new T(2) - Tracer
@Tracer:当 p2 被分配时,它所做的事情。 - GManNickG
@Tracer:p2 的移动赋值运算符在从 p 移动 new T(1)p2 之前必须销毁 new T(2) - Jon Purdy
@JonPurdy:我不确定那是否准确。我在想,将该赋值实现为交换是否合法(且高效),因为p被移动了。 - MSalters
@MSalters:我认为你可以将其实现为一次交换,然后是一次释放,只要交换也是noexcept的。§20.7.1¶3说:“让符号u.p表示由u存储的指针,让u.d表示相关的删除器。在请求时,u可以重置(替换)u.pu.d为另一个指针和删除器,但必须通过相关的删除器正确处理其拥有的对象,然后才能认为完成了这种替换。” - Jon Purdy

2

unique_ptr被定义为确保第一个int得到正确地释放,因此它会调用delete来释放已保留的内存。

这与下面的代码有些相似:

int* p = new int(1);
delete p;
p = new int(2);

详细的过程如下:
  • 使用new int(1)创建一个新的整数。
  • 将指向这个新int的指针传递给刚创建的unique_ptr实例,名为p。这是一个暂时只存储指针的对象。
  • 使用new int(2)创建第二个int。
  • 使用unique_ptr<int>(new int(2))将此指针传递给新的unique_ptr。这是一个临时的unique_ptr实例(稍后我们会看到原因),它存储了指向第二个int的指针。
  • 将临时对象分配给p。现在,赋值运算符被定义为删除先前拥有的对象(第一个int),并取得由分配的unique_ptr拥有的对象(第二个int)。以下是一种实现方法。此时,p拥有第二个int,第一个int已被删除,临时不再拥有任何对象(持有nullptr)。
  • 最后,临时unique_ptr超出作用域,因为我们从未给它命名或存储引用,所以调用了它的析构函数。但它只包含nullptr
因此,使用原始指针的更详细等价物可能如下:
int* p = new int(1);  //create an int
{
    int* tmp = new int(2);  //create second int
    int* del = p; //we need to delete this (first int)

    //take ownership of the temporary (second int)
    p = tmp; 
    tmp=nullptr;

    //delete the old object (first int)
    delete del;
}  //tmp and del go out of scope here, but tmp holds the nullptr and del is deleted
//first int is deleted, p points to the second int here

为Tracer编辑: 这是Visual Studio使用的实现方式(注释也是<memory>的一部分):

typedef unique_ptr<_Ty> _Myt;

_Myt& operator=(_Myt&& _Right) _NOEXCEPT
{   // assign by moving _Right
    if (this != &_Right)
    {   // different, do the move
        reset(_Right.release());
        this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
    }
return (*this);
}

void reset(pointer _Ptr = pointer()) _NOEXCEPT
 {  // establish new pointer
    pointer _Old = get();
    this->_Myptr() = _Ptr;
    if (_Old != pointer())
        this->get_deleter()(_Old);
 }

交换指针?你确定吗?C++标准中是否有支持该语句的内容? - Tracer
@Tracer:我和shared_ptr混淆了。在Visual Studio附带的shared_ptr实现中,临时shared_ptr在赋值运算符中进行复制构造。然后,当前指针与临时指针交换。当临时对象超出作用域时,shared_ptr本身持有分配的值,临时对象则持有旧值,减少其引用计数/销毁对象。但由于这个问题是关于unique_ptr的,我已经相应地更新了答案。 - Anedar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接