首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >std::unique_ptr重置()操作顺序

std::unique_ptr重置()操作顺序
EN

Stack Overflow用户
提问于 2021-09-01 00:40:27
回答 2查看 1.1K关注 0票数 10

调用void reset( pointer ptr = pointer() ) noexcept;调用以下操作

给定current_ptr,由*this管理的指针按照以下顺序执行以下操作:

  1. 保存当前指针old_ptr = current_ptr的副本
  2. 用参数current_ptr = ptr覆盖当前指针
  3. 如果旧指针不是空的,则删除以前托管的对象if(old_ptr) get_deleter()(old_ptr)。

优先选择

这个特别订单的原因是什么?为什么不直接做然后再做呢?在这个问题中,ptr::重置检查托管指针无效?的第一个答案引用了标准

这是唯一的原因吗?get_deleter()如何摧毁unique_ptr (*this)?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-09-07 22:14:21

在分析规定的步骤顺序时,考虑哪些步骤可以抛出,以及什么状态会使一切都处于这样的状态,这通常是有用的--这样做的目的是我们永远不应该在无法恢复的情况下结束。

请注意,在docs 这里中:

std::shared_ptr不同,std::unique_ptr可以通过满足NullablePointer的任何自定义句柄类型来管理对象。例如,这允许通过提供定义Deletertypedef boost::offset_ptr pointer或另一个高级指针来管理位于共享内存中的对象。

因此,按照目前的顺序:

  1. 保存当前指针old_ptr = current_ptr的副本 如果抛出花式指针的复制构造函数,unique_ptr仍然拥有原始对象,并且新对象未被拥有: OK
  2. 用参数current_ptr = ptr覆盖当前指针 如果花哨指针的复制赋值抛出,unique_ptr仍然拥有原始对象,新对象不属于: OK (这假设花哨指针的副本赋值操作符满足通常的异常安全保证,但是没有它,unique_ptr就无能为力了)
  3. 如果旧指针不是空的,则删除以前托管的对象if(old_ptr) get_deleter()(old_ptr)。 在这个阶段,unique_ptr拥有新对象,删除旧对象是安全的。

换言之,这两种可能的结果是:

代码语言:javascript
复制
std::unique_ptr<T, FancyDeleter> p = original_value();
try {
  auto tmp = new_contents();
  p.reset(tmp);
  // success
}
catch (...) {
  // p is unchanged, and I'm responsible for cleaning up tmp
}

按照你提议的顺序:

  1. 如果原始指针是非空的,请删除它。 在此阶段,unique_ptr无效:它已提交了不可逆转的更改(删除),如果下一步失败,则无法恢复良好的状态。
  2. 用参数current_ptr = ptr覆盖当前指针 如果花哨指针的副本赋值抛出,我们的unique_ptr将不可用:存储的指针不确定,无法恢复旧的指针

换句话说,我所说的无法恢复的情况如下所示:

代码语言:javascript
复制
std::unique_ptr<T, FancyDeleter> p = original_value();
try {
  auto tmp = new_contents();
  p.reset(tmp);
  // success
}
catch (...) {
  // I can clean up tmp, but can't do anything to fix p
}

在该异常之后,p甚至无法被安全销毁,因为调用其内部指针上的删除器的结果可能是双空闲的。

注意:删除器本身是不允许抛出的,所以我们不必担心。

纸条上写着

..。对get_­deleter()的调用可能会破坏*this

听起来不对,但get_­deleter()(old_­p)的电话真的可能.如果*old_p是一个对象,它本身包含一个unique_ptr。在这种情况下,删除器调用必须是最后一个调用,因为从字面上说,在此之后您无法安全地对unique_ptr实例执行任何操作。

虽然这个角落的情况是将删除器调用放在末尾的一个坚实的原因,但我觉得强大的异常安全论点可能没有那么人为的(尽管与抛出赋值的花式指针相比,具有唯一指针的对象或多或少是比较常见的)。

票数 3
EN

Stack Overflow用户

发布于 2021-09-07 19:34:47

类似,您可以创建使用std::unique_ptr管理自己生命周期的对象,而无需std::shared_ptr的开销,只要需要std::unique_ptr。在下面的例子中,我们创建了一个“火与忘”的任务,自毁后,它的工作。动机可以找到这里

代码语言:javascript
复制
#include <cstdio>
#include <future>
#include <memory>
#include <thread>

using namespace std::chrono_literals;

class Task
{
  public:
    Task(Task&&) = delete;
    Task& operator=(Task&&) = delete;
    Task(const Task&) = delete;
    Task& operator=(const Task&) = delete;
    ~Task() { std::printf("Destroyed.\n"); }

    static Task* CreateTask()
    {
        // Can't use std::make_unique because the constructor is private.
        std::unique_ptr<Task> task{new Task{}};
        Task* const result{task.get()};
        result->AcceptOwnershipOfSelf(std::move(task));
        return result;
    }

    void Run()
    {
        // Do work ...

        // Work is done. Self-destruct.
        self_.reset();
    }

  private:
    // Constructor needs to be private: Task must be created via `CreateTask`.
    Task() = default;
    void AcceptOwnershipOfSelf(std::unique_ptr<Task> self) { self_ = std::move(self); }

    std::unique_ptr<Task> self_;
};

int main()
{
    Task* const task{Task::CreateTask()};
    std::ignore = std::async(&Task::Run, task);
    std::this_thread::sleep_for(1s);
}

哥德波特。请注意“毁灭”。只印了一次。

为什么不直接做然后再做呢?

如果self_.reset();要在对current_ptr进行零化之前删除以前托管的指针,那么self_仍然会指向~Task中的*this --导致无限循环。我们可以通过将self_.reset();替换为self_->~Task();来看到这一点。

当然,我们可以用以下两行代码替换self_.reset();

代码语言:javascript
复制
        std::unique_ptr<Task> tmp{std::move(self_)};
        tmp.reset();

我不知道委员会是否因为这个用例而决定像他们那样指定reset

备注:

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69006612

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档