在从矢量中删除之前,是否需要重置shared_ptr?

时间:2014-05-15 18:19:49

标签: c++ shared-ptr smart-pointers

我使用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'来销毁共享指针。我想如果我只是在没有调用函数'reset'的情况下销毁节点,那么共享指针仍然应该被销毁。是吗?

另外,我不明白为什么函数'RemoveByName__CLASSIC__NOT_OK'失败。我不明白为什么我必须在循环期间声明一个'It_dest'来存储迭代器(参见方法'RemoveByName__CLASSIC__OK'),最后擦除函数末尾的节点。这个问题恰好发生在共享指针上。有没有人有想法?

2 个答案:

答案 0 :(得分:4)

您不必手动重置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的实现更具可读性。

答案 1 :(得分:1)

RemoveByName__CLASSIC__NOT_OK执行未定义的行为。

当您从std::vector中删除时,所有迭代器和对 元素 之后的元素的引用都将失效。这意味着它们不能被解除引用或比较,或者在不调用未定义行为的情况下安全地覆盖它们。

现在,UB经常“做你觉得它应该神奇地做的事情”,所以失败并没有帮助你。

如果发生这种情况,如果RemoveByName__CLASSIC__NOT_OKbreak之后立即执行erase,那么它就会很明确。

RemoveByName__CLASSIC__OK推迟擦除,直到完成迭代。它有许多问题,包括如果不存在具有该名称的元素则执行未定义的行为,不处理重复的名称等。它会删除与名称匹配的 last 元素(如果存在),否则会未定义的行为。您可能需要每个元素,和/或删除您发现的第一个以节省时间。 (如果你真的想要最后,请向后迭代并删除你找到的第一个。)

在销毁.reset()之前

shared_ptr会将对象的可能破坏移至.reset(),而不是在std::shared_ptr的内部,有时 非常有用(因为当您处于胆量状态时,对std::vector的任何和所有访问都是UB)。我要做的一件事是swapmove shared_ptr出容器,.erase来自容器,然后是.reset或者只是让本地{ {1}}副本超出范围。

您的shared_ptr也会被破坏并且可能会出现未定义的行为,如果找到除了与谓词匹配的1个元素之外的任何内容,则基本上会出错。将RemoveByName__PREDICATE__OK子句末尾的);更改为erase,以便不会删除一个元素,而是删除从, resourceList.end());的返回值到结尾的所有内容。 remove_if