unique_ptr<T>::get() 返回的指针在原始 unique_ptr 被销毁后不是 nullptr

Tur*_*bar 2 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*/
}
Run Code Online (Sandbox Code Playgroud)

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

struct Base
{
  ...
  std::vector<Base*> providers;
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

void Base::subscribe_to(Base* src)
{
  providers.push_back(src);
}
Run Code Online (Sandbox Code Playgroud)

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

void Base::do_work()
{
  ...
  for(Base* ptr : providers)
  {
    if(ptr != nullptr)
    {
      ...
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,在我的代码中的其他地方,我可以删除 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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Rem*_*eau 5

我希望擦除一个unique_ptrfromglobal_vec会调用Base::~Base()

是的,它确实。

从而将指针呈现Base::providersstd::nullptr

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

您可能会考虑vector<Base*>在您的Base类中使用两个,一个用于跟踪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;
Run Code Online (Sandbox Code Playgroud)

否则,请考虑在全局向量中使用std::shared_ptr而不是std::unique_ptr,然后您可以在其他向量中存储std::weak_ptr<Base>对象而不是原始Base*指针。当您访问 a 时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;
Run Code Online (Sandbox Code Playgroud)

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

不,它们无效,因为指向的对象已被销毁。指针只是悬空,指向旧内存,并不nullptr像您期望的那样。

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

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