STL擦除 - 删除习语与自定义删除操作和valgrind

时间:2010-07-18 14:57:35

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

这是尝试使用STL算法而不是手写循环等来重写一些旧作业。

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

CD
Artist
Album
Idnumber
Book
Author
Title
Idnumber
Book
...
...

使用手写循环时,删除这些对象可以正常工作:编辑:对不起我说得太快,实际上并不是一个'手写'循环本身...我一直在编辑项目以删除手写的循环,这实际上使用find_if算法和手动删除,但问题是有效的。 /编辑。

typedef vector<Media *>::iterator v_iter;

...
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);
    }
}

这应该是非常明显的 - 如果找到与参数匹配的id,则仿函数返回true,并且对象被销毁。这工作和valgrind报告没有内存泄漏,但由于我想使用STL,显而易见的解决方案应该是使用擦除删除习惯用法,导致如下所示

void Database::removeById(int id) {
    v_.erase(remove_if(v_.begin(), v_.end(), Comparer(id)), v_.end());
};

然而,这个“有效”但根据valgrind导致内存泄漏,那么是什么给出了?第一个版本工作正常,没有任何泄漏 - 而这个版本总是显示3个allocs'not freed'为我删除的每个Media对象。

7 个答案:

答案 0 :(得分: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的有效观察

答案 1 :(得分:3)

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

答案 2 :(得分:3)

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

答案 3 :(得分:2)

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

(如果您的编译器尚不支持std::shared_ptr,则很可能会std::tr1::shared_ptr。否则请使用boost's boost::shared_ptr。)

答案 4 :(得分: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);
    }
}

希望这样可以更清楚地了解发生了什么,以及泄漏出现的原因。

答案 5 :(得分:1)

显然,你的vector OWNS包含它所包含的对象。

正如您所注意到的那样,STL容器在它们不容易适应继承的意义上不是OO友好的。你需要使用动态分配的内存及其所有的麻烦。

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

您还应该考虑另一种解决方案。容器模仿STL,但设计用于继承层次结构。他们在引擎盖下使用指针(很明显),但让你自己处理内存管理和与智能指针相关的开销(特别是shared_ptr)。

看看Boost Pointer Container图书馆!

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

此外,它的容器(我认为)符合STL标准,因此您可以使用erase/remove成语,find_if算法等...

答案 6 :(得分:0)

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

typedef vector< shared_ptr<Media> > MediaVector;
...