【问题标题】:Pointer returned by unique_ptr<T>::get() is not nullptr after original unique_ptr is destroyedunique_ptr<T>::get() 返回的指针在原始 unique_ptr 被销毁后不是 nullptr
【发布时间】:2018-08-10 21:44:26
【问题描述】:

我有一个基类的 unique_ptrs 全局向量,我将 unique_ptrs 附加到派生类:

std::vector<std::unique_ptr<Base>> global_vec;

template<typename T>
Base* create_object()
{
  std::unique_ptr<T> uptr = std::make_unique<T>(/* ... */);
  Base* last_ptr = uptr.get();
  global_vec.emplace_back(std::move(uptr));
  return last_ptr; /*this is a bit irrelevant, but is for the caller*/
}

现在,Base 本身有一个指向 Base 的原始指针的成员向量:

struct Base
{
  ...
  std::vector<Base*> providers;
  ...
}

构成Base::providers的指针都是通过从global_vec调用unique_ptr::get()获得的:

void Base::subscribe_to(Base* src)
{
  providers.push_back(src);
}

Base 有一个与这些订阅者一起工作的成员函数,并在工作之前检查 nullptr:

void Base::do_work()
{
  ...
  for(Base* ptr : providers)
  {
    if(ptr != nullptr)
    {
      ...
    }
  }
}

现在,在我的代码的其他地方,我可以擦除 global_vec 中的 unique_ptrs:

auto itr = std::find_if(global_vec.begin(), global_vec.end(), [&](std::unique_ptr<Base> const& n)
        { return n.get() == ptr_selected; }); //ptr_selected points to a widget selected by the user
  global_vec.erase(itr);

然而,在擦除一个元素之后,Base::suscribers 仍然会持有一个指向对象的有效指针。也就是说,在 Base::do_work() 中迭代 Base::providers 时,没有 Base* 将等于 std::nullptr。

我希望从 global_vec 中删除 unique_ptr 会调用 Base::~Base(),从而将 Base::providers 中的指针呈现为 std::nullptr。 Base 的析构函数被调用,但指针是有效的(你甚至可以从它们中访问数据成员)。

Base 确实有一个虚拟析构函数。

为什么 Base::providers 中的指针仍然有效?

【问题讨论】:

  • get 按值返回!
  • 销毁一个对象对指向该对象的指针没有影响,除非取消引用它们是未定义的。并且 undefined 有时似乎会起作用(无法确定指针是否有效)。
  • 指针是一种方式。没有反向通道来更新指向同一对象的其他指针。实施这种渠道的簿记是不切实际的。

标签: c++ inheritance unique-ptr


【解决方案1】:

我希望从global_vec 中删除unique_ptr 会调用Base::~Base()

是的,确实如此。

因此将Base::providers 中的指针渲染为std::nullptr

这是您的期望失败的地方。当对象被销毁时,无法将指向对象的 raw 指针自动设置为 nullptr有责任在您自己的代码中手动处理。在销毁相应对象之前/时,您需要从 providers 向量中删除 Base* 指针。编译器无法为您做到这一点。

您可以考虑在Base 类中设置两个vector&lt;Base*&gt;,一个用于跟踪this 已订阅的对象,另一个用于跟踪已订阅this 的对象。然后,~Base() 可以从活动订阅中取消订阅this,并通知活动订阅者this 即将消失。例如:

struct Base
{
...
protected:
    std::vector<Base*> providers;
    std::vector<Base*> subscribers;
    ...

public:
    ~Base();

    ...

    void subscribe_to(Base* src);
    void unsubscribe_from(Base* src);

    ...
};

Base::~Base()
{
    std::vector<Base*> temp;

    temp = std::move(providers);
    for(Base* ptr : temp) {
        unsubscribe_from(ptr);
    }

    temp = std::move(subscribers);
    for(Base* ptr : temp) {
        ptr->unsubscribe_from(this);
    }
}

void Base::subscribe_to(Base* src)
{
    if (src) {
        providers.push_back(src);
        src->subscribers.push_back(this);
    }
}

void Base::unsubscribe_from(Base* src)
{
    if (src) {
        std::remove(providers.begin(), providers.end(), src);
        std::remove(src->subscribers.begin(), src->subscribers.end(), this);
    }
}

void Base::do_work()
{
    ...
    for(Base* ptr : providers) {
        ...
    }
    ...
}

...

std::vector<std::unique_ptr<Base>> global_vec;

否则,请考虑在全局向量中使用std::shared_ptr 而不是std::unique_ptr,然后您可以在其他向量中存储std::weak_ptr&lt;Base&gt; 对象而不是原始Base* 指针。当您访问std::weak_ptr时,您可以在使用该指针之前对其进行查询以确保关联的对象指针仍然有效:

struct Base
{
...
protected:
    std::vector<std::weak_ptr<Base>> providers;
    ...
public:
    ...

    void subscribe_to(std::shared_ptr<Base> &src);

    ...
};

void Base::subscribe_to(std::shared_ptr<Base> &src)
{
    if (src) {
        providers.push_back(src);
    }
}

void Base::do_work()
{
    ...
    for(std::weak_ptr<Base> &wp : providers) {
        std::shared_ptr<Base> ptr = wp.lock();
        if (ptr) {
            ...
        }
    }
    ...
}

...

std::vector<std::shared_ptr<Base>> global_vec;

Base 的析构函数被调用,但指针有效

不,它们不是有效,因为指向的对象已被破坏。指针只是悬空,指向旧内存,而不是像您期望的那样nullptr

您甚至可以从他们那里访问数据成员

在对象被销毁后访问其成员是未定义的行为

【讨论】:

    猜你喜欢
    • 2014-10-25
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 2014-10-21
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-13
    相关资源
    最近更新 更多