【问题标题】:Do I need to reset a shared_ptr before removing it from a vector?在从向量中删除它之前是否需要重置 shared_ptr ?
【发布时间】:2014-07-04 08:24:49
【问题描述】:

我使用 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”),最后在函数结束时擦除节点。这个问题只是使用共享指针发生的。有人有想法吗?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 感谢您的回答
    【解决方案2】:

    RemoveByName__CLASSIC__NOT_OK 执行未定义的行为。

    当您从 std::vector 擦除时,所有迭代器和对擦除点 at 之后的元素的引用都将失效。这意味着它们不能被取消引用或比较或其他任何东西,只能在不调用未定义行为的情况下安全地覆盖。

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

    碰巧的是,如果RemoveByName__CLASSIC__NOT_OKerase 之后立即执行break,则定义良好。

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

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

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

    【讨论】:

    • 非常感谢您的回答。
    猜你喜欢
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 2011-08-27
    • 2019-10-20
    • 1970-01-01
    相关资源
    最近更新 更多