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

Pointer returned by unique_ptr<T>::get() is not nullptr after original unique_ptr is destroyed

本文关键字:unique ptr 原始 nullptr gt lt get 返回 指针      更新时间:2023-10-16

我有一个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::p roviders 的指针都是通过从 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::p roviders 中的 Base::d o_work(( 时,没有 Base* 等于 std::nullptr。

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

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

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

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

是的,确实如此。

从而将Base::providers中的指针呈现为std::nullptr

这就是你的期望失败的地方。 无法将指向对象的原始指针设置为在销毁对象时自动nullptr有责任在自己的代码中手动处理它。 您需要在销毁相应对象之前/当相应对象被销毁时从providers向量中删除Base*指针。 编译器无法为您执行此操作。

您可以考虑在Base类中设置两个vector<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;

否则,请考虑在全局向量中使用std::shared_ptr而不是std::unique_ptr,然后可以在其他向量中存储std::weak_ptr<Base>对象而不是原始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

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

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