我需要重置一个shared_ptr之前从一个矢量删除它

Do I need to reset a shared_ptr before removing it from a vector?

本文关键字:一个 删除 ptr shared      更新时间:2023-10-16

我用std::shared_ptr写了一个非常简单的c++程序。

代码如下:

/*
** Resource class definition
*/
class Resource
{
    public:
        std::string m_Name;
        Resource(void){}
        Resource(std::string name)
            :   m_Name(name)
        {
        }
        std::string const &GetName(void) const
        {
            return (this->m_Name);
        }
};
namespace Predicate
{
    /*
    ** Predicate - Delete a specific node according to its name
    */
    template <typename T>
    struct DeleteByName
    {
        DeleteByName(std::string const &name);
        bool operator()(T &pData);
        std::string m_Name;
    };
    //Initialization
    template <typename T>
    DeleteByName<T>::DeleteByName(std::string const &name)
        :   m_Name(name)
    {
    }
    //Surcharges
    template <typename T>
    bool DeleteByName<T>::operator()(T &pData)
    {
        if (pData->GetName() == this->m_Name)
        {
            pData.reset();
            return (true);
        }
        return (false);
    }
}
/*
** Remove a specific node according to its name - WORKS
*/
static void RemoveByName__CLASSIC__OK(std::string const &name, std::vector<std::shared_ptr<Resource>> &resourceList)
{
    std::vector<std::shared_ptr<Resource>>::iterator It = resourceList.begin();
    std::vector<std::shared_ptr<Resource>>::iterator It_dest;
    for (; It != resourceList.end(); ++It) {
        if (!(*It)->GetName().compare(name))
        {
            It_dest = It;
        }
    }
    It_dest->reset();
    resourceList.erase(It_dest);
}
/*
** Remove a specific node according to its name - NOT WORK
*/
static void RemoveByName__CLASSIC__NOT_OK(std::string const &name, std::vector<std::shared_ptr<Resource>> &resourceList)
{
    std::vector<std::shared_ptr<Resource>>::iterator It = resourceList.begin();
    for (; It != resourceList.end(); ++It) {
        if (!(*It)->GetName().compare(name))
        {
            It->reset();
            resourceList.erase(It);
        }
    }
}
static std::vector<std::shared_ptr<Resource>>::const_iterator FindByName__PREDICATE__OK(
    std::string const &name, std::vector<std::shared_ptr<Resource>> &resourceList)
{
    return (std::find_if(resourceList.begin(),
            resourceList.end(), Predicate::FindByName<std::shared_ptr<Resource>>(name)));
}
/*
** Remove a specific node according to its name using std::remove_if algorithm with the predicate 'DeleteByName' - WORKS
*/
static void RemoveByName__PREDICATE__OK(std::string const &name, std::vector<std::shared_ptr<Resource>> &resourceList)
{
    if (FindByName__PREDICATE__OK(name, resourceList) != resourceList.end())
        resourceList.erase(std::remove_if(
            resourceList.begin(), resourceList.end(), Predicate::DeleteByName<std::shared_ptr<Resource>>(name)));
}
/*
** Entry point
*/
int main(void)
{
    std::vector<std::shared_ptr<Resource>> resourceList;
    std::shared_ptr<Resource> rsc_A(new Resource("resource_a"));
    std::shared_ptr<Resource> rsc_B(new Resource("resource_b"));
    std::shared_ptr<Resource> rsc_C(new Resource("resource_c"));
    resourceList.push_back(rsc_A);
    resourceList.push_back(rsc_B);
    resourceList.push_back(rsc_C);
    PrintResourceList(resourceList);
    RemoveByName__PREDICATE__OK("resource_as", resourceList);
    PrintResourceList(resourceList);
    getchar();
    return (0);
}

我只是想知道我是否从包含共享指针的std::vector中擦除节点,如果我必须在调用'erase'方法之前调用'reset'方法来销毁共享指针。我认为,如果我只是销毁节点,而不调用函数"重置",共享指针应该仍然被销毁。对吗?

另外,我不明白为什么函数'RemoveByName__CLASSIC__NOT_OK'失败了。我不明白为什么我必须声明一个'It_dest'来在循环期间存储迭代器(参见方法'RemoveByName__CLASSIC__OK'),并最终在函数结束时擦除节点。这个问题只是在使用共享指针时发生的。有人知道吗?

您不必手动重置shared_ptr,这是在析构函数中完成的。当您擦除它时,对象将被销毁,从而减少引用计数。

您的RemoveByName__CLASSIC__NOT_OK函数失败,因为您在擦除指向元素后使用迭代器。在std::vector::erase之后,迭代器将失效,不能再使用。erase返回下一个迭代器

static void RemoveByName__CLASSIC__NOT_OK(std::string const &name, std::vector<std::shared_ptr<Resource>> &resourceList)
{
    for (auto It = resourceList.begin(); 
         It != resourceList.end(); ) {
        if (!(*It)->GetName().compare(name))
        {
            It = resourceList.erase(It);
        }
        else
        {
            ++It;
        }
    }
}

我认为使用remove_if的实现更具可读性。

RemoveByName__CLASSIC__NOT_OK执行未定义行为

当从std::vector中擦除时,所有迭代器和对处或后元素的引用都无效。这意味着它们不能被解引用、比较或其他任何操作,只能在不调用未定义行为的情况下安全地覆盖。

现在,UB经常"神奇地完成你认为它应该做的事情",所以失败的崩溃对你没有帮助。

碰巧的是,如果RemoveByName__CLASSIC__NOT_OKerase之后立即执行break,那么它将被很好地定义。

RemoveByName__CLASSIC__OK将擦除延迟到迭代完成之后。它有许多问题,包括执行未定义的行为,如果具有该名称的元素不存在,不处理重复的名称,等等。如果最后一个元素存在,则擦除与名称匹配的元素,否则执行未定义行为。您可能希望每个元素都,并且/或首先擦除以节省时间。(如果你真的想要最后一个,向后迭代并擦除你找到的第一个)。

在销毁shared_ptr之前,

.reset()将对象的可能销毁移动到.reset(),而不是在std::shared_ptr的内部,这有时可能是有用的(因为当您在内部时对std::vector的任何和所有访问都是UB)。我经常做的一件事是swapmoveshared_ptr从容器中取出,.erase将其从容器中取出,然后.reset或只是让本地shared_ptr副本超出范围。

你的RemoveByName__PREDICATE__OK也坏了,可以表现出未定义的行为,基本上做错误的事情,如果除了1个元素匹配谓词被发现。将erase子句末尾的);更改为, resourceList.end());,这样就不会擦除一个元素,而是擦除从remove_if返回值到vector末尾的所有元素。