【问题标题】:STL erase-remove idiom vs custom delete operation and valgrindSTL 擦除删除习语与自定义删除操作和 valgrind
【发布时间】:2011-03-17 14:26:53
【问题描述】:

这是尝试使用 STL 算法而不是手写循环之类的东西来重写一些旧作业。

我有一个名为 Database 的类,它包含一个 Vector<Media *>,其中 Media * 可以是(除其他外)一张 CD 或一本书。数据库是唯一处理动态内存的类,当程序启动时,它会读取一个格式如下所示(有些简化)的文件,在读取条目时分配空间,并将它们添加到上面的向量 (v_) 中。

光盘 艺术家 专辑 身份证号码 书 作者 标题 身份证号码 书 ... ...

使用手写循环时删除这些对象按预期工作:编辑:对不起,我说得太早了,这实际上并不是一个“手写”循环本身。我一直在编辑项目以删除手- 编写循环,这实际上使用 find_if 算法和手动删除,但问题是有效的。 /编辑。

typedef vector<Media *>::iterator v_iter;

... 无效数据库::removeById(int id) { v_iter it = find_if(v_.begin(), v_.end(), Comparer(id)); 如果(它!= v_.end()){ 删除它; v_.erase(它); } }

这应该是不言自明的——如果函子找到与参数匹配的 id,则函子返回 true,并且对象被销毁。这有效并且 valgrind 报告没有内存泄漏,但由于我想使用 STL,明显的解决方案应该是使用擦除删除成语,导致如下所示

无效数据库::removeById(int id) { v_.erase(remove_if(v_.begin(), v_.end(), Comparer(id)), v_.end()); };

然而,这“有效”但根据 valgrind 会导致内存泄漏,那么给出了什么?第一个版本运行良好,完全没有泄漏 - 而这个版本总是为我删除的每个 Media 对象显示 3 个“未释放”的分配。

【问题讨论】:

  • 不知道你是否注意到了,但 remove_if 版本的功能与 find_if 版本不同。 find_if 版本删除第一个匹配 id 的元素,remove_if 版本删除所有匹配 id 的元素

标签: c++ stl std idioms dynamic-memory-allocation


【解决方案1】:

在第一个版本中,您小心地调用delete *it。在更新的版本中,您不是......v_.erase 正在取消分配指针,而不是指针引用的对象。

【讨论】:

  • 哦,当然!但是有没有办法使用一些 STL 算法使第二个版本按预期工作而不显式调用删除?还是我在这里停留在第一个版本?
【解决方案2】:

这意味着第二个版本不正确。如果你想使用它,考虑shared_ptr:

typedef 向量 > MediaVector; ...

【讨论】:

    【解决方案3】:

    您已经have a specific answer。但是,您的根本问题是您使用裸指针来手动管理资源。这很难,有时会很痛。
    将您的类型更改为std::vector<std::shared_ptr<Media> >,事情就变得容易多了。

    (如果你的编译器还不支持std::shared_ptr,它很可能有std::tr1::shared_ptr。否则使用boost'sboost::shared_ptr。)

    【讨论】:

    • 我不推荐 shared_ptr 本身,而是推荐 unique_ptr
    • @Matthieu:实际上是否存在不需要容器元素类型是可复制构造和可分配的 STL 实现?
    • 好问题 :) 好吧,我想会是这样,因为标准要求支持移动语义,但我不知道实际的实现状态。我想这是指针容器库的另一种情况:我不喜欢为此使用shared_ptr,因为存在泄露所有权的风险......
    • @Matthieu:发明了弱指针,这样您就不会冒险泄露所有权。
    • 但是取消引用迭代器会产生shared_ptr 的副本,而不是weak_ptr,因此您的容器实际上是泄漏所有权。所以当然你可能会很小心,把容器整齐地塞进它自己的类中......但我认为使用指针容器库仍然更容易。
    【解决方案4】:

    removeById 的第二个版本中没有对delete 的调用。从指针向量中擦除元素不会在指针上调用delete

    提供的erase-remove版本大致相当于使用原始removeById,但有一个小的变化:

    void Database::removeById(int id) {
        v_iter it = find_if(v_.begin(), v_.end(), Comparer(id));
        if (it != v_.end()) {
            //delete *it;
            v_.erase(it);
        }
    }
    

    希望这可以更清楚地说明发生了什么,以及为什么会出现泄漏。

    【讨论】:

    • 是的,我认为对于 remove_if 迭代器返回的任何对象,erase 都会调用 delete。学过的知识。谢谢!
    • erase-remove 版本不等同于 find_if 版本,请参阅我对问题的评论。
    • 你说的很对,虽然我在里面加了一个“大致”这样我就可以说我是对的,即使我不是;)
    【解决方案5】:

    这就是为什么你应该总是,总是使用智能指针。出现问题的原因是因为您使用了一个哑指针,将其从向量中删除,但这并没有触发释放它指向的内存。相反,您应该使用始终释放指向的内存的智能指针,其中从向量中删除等于释放指向的内存。

    【讨论】:

    • 谢谢!所有的答案都很好,我会接受这个,因为它提供了一个解决方案。
    • 智能指针并非总是适合 STL 容器,尤其是您的项目。 YMMV
    • @Blair:这不是真的。事实是,STL 智能指针并不总是适合 STL 容器。你总是可以自己动手。此外,在 C++0x 中,其中一些东西是固定的,比如新的 std::unique_ptr。
    • 智能指针如果没有其他库就无法使用,则没有意义。并非所有编译器都有智能指针。 (VC2008 没有,gcc 没有)。如果您只是在一个可以安全不使用它们的地方使用它,那么滚动您自己的智能指针可能会更麻烦。
    【解决方案6】:

    显然你的vector拥有它所包含的对象。

    正如您所注意到的,STL 容器对 OO 不友好,因为它们不容易适应继承。您需要使用动态分配的内存及其所有的麻烦。

    第一个也是简单的解决方案是用智能指针替换普通指针(请不要再用了)。人们通常会推荐shared_ptr,但如果您可以访问C++0x,则更喜欢他们unique_ptr

    您还应该考虑另一种解决方案。模仿 STL 的容器,但设计用于继承层次结构。它们在后台使用指针(显然),但让您摆脱了自己管理内存的乏味以及与智能指针相关的开销(尤其是 shared_ptr)。

    Boost Pointer Container 库!

    这显然是最好的解决方案,因为它完全是为您要解决的问题而创建的。

    此外,它的容器(我认为大多数情况下)符合 STL,因此您将能够使用 erase/remove 成语、find_if 算法等...

    【讨论】:

      【解决方案7】:

      规则是:如果您对包含指针并且是指针所有者的向量应用 remove(),则会泄漏内存。

      Scott Meyers 的“Effective STL”中有一个很好的解决方案。而且它不涉及智能指针!仅使用智能指针来进行擦除删除工作是一种过度杀伤力。

      在这里(来自书中,第 33 条)。任务是有选择地从 isCertified() 返回 false 的向量小部件中删除。

      class Widget
      {
        public:
          isCertified() const;
        ...
      };
      
      void delAndNullifyUncertified(Widget*& pWidget)
      {
        if (!pWidget->isCertified())
        {
          delete pWidget;
          pWidget = 0;
        }
      }
      
      vector<Widget *> v;
      v.push_back(new Widget);
      ...
      // set to NULL all uncertified widgets
      for_each(v.begin(), v.end(), delAndNullifyUncertified);
      // eliminate all NULL pointers from v
      v.erase(remove(v.begin(), v.end(), static_cast<Widget *>(0)),
              v.end();
      

      我希望这会有所帮助。

      编辑(2012 年 8 月 28 日):反映来自 Slavik81 的有效观察结果

      【讨论】:

      • 我提醒您,您的规则仅在向量是分配内存的所有者时才成立(在这种情况下就是如此)。但是,作为不熟悉 C++ 标准算法的人,我发现您的回答非常有用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-23
      相关资源
      最近更新 更多