在销毁原始unique_ptr之后,由unique_ptr <t> :: get()返回的指针不是nullptr

时间:2018-08-10 21:44:26

标签: c++ inheritance unique-ptr

我在基类上有一个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中的指针仍然有效?

1 个答案:

答案 0 :(得分:3)

  

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

是的。

  

因此将Base::providers中的指针显示为std::nullptr

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

您可能考虑在您的vector<Base*>类中有两个Base,一个用于跟踪this已订阅的对象,另一个用于跟踪已订阅{ {1}}。然后,this可以取消订阅~Base()的活动订阅,并通知活动订阅者this即将消失。例如:

this

否则,请考虑在全局向量中使用std::shared_ptr而不是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::unique_ptr对象,而不是原始的std::weak_ptr<Base>指针。访问std::weak_ptr时,可以查询它以确保关联的对象指针在使用该指针之前仍然有效:

Base*
  

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

否,它们不是有效,因为指向的对象已被破坏。指针只是悬空而指向旧的内存,并且不像您期望的那样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;

  

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

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