3

这是使用 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 个“未释放”的分配。

4

7 回答 7

4

规则是:如果您在包含指针并且是指针所有者的向量上应用 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的有效观察

于 2012-08-27T23:42:55.220 回答
3

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

于 2010-07-18T15:05:51.330 回答
3

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

于 2010-07-18T15:08:57.997 回答
2

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

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

于 2010-07-18T15:08:52.513 回答
1

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

提供的擦除删除版本大致相当于使用原始版本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);
    }
}

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

于 2010-07-18T15:08:54.073 回答
1

显然,您vector拥有它包含的对象。

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

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

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

看看Boost Pointer Container库!

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

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

于 2010-07-19T06:59:16.503 回答
0

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

typedef 向量< shared_ptr<Media> > MediaVector;
...
于 2010-07-18T15:08:01.313 回答