【问题标题】:Cleaning up an STL list/vector of pointers清理 STL 列表/指针向量
【发布时间】:2010-09-23 08:28:46
【问题描述】:

为了安全地清理 std::vectorstd::list 的指针,你能想到的最短的 C++ 块是什么? (假设你必须在指针上调用 delete?)

list<Foo*> foo_list;

我宁愿不使用 Boost 或用智能指针包装我的指针。

【问题讨论】:

  • 依赖容器外的代码来删除你的指针真的很危险。例如,当容器由于抛出异常而被破坏时会发生什么?我知道你说过你不喜欢 boost,但请考虑boost pointer containers
  • 智能指针(包括 Boost::shared_ptr)会在您很难看到它是手动完成的情况下删除您的对象。
  • 其中一个陷阱是,反常的是,STL 允许几个重要的迭代器操作抛出异常。这使得许多通过容器使用迭代的“明显”方法不安全。见stackoverflow.com/questions/7902452/…

标签: c++ list vector stl


【解决方案1】:

对于std::list&lt;T*&gt; 使用:

while(!foo.empty()) delete foo.front(), foo.pop_front();

对于std::vector&lt;T*&gt; 使用:

while(!bar.empty()) delete bar.back(), bar.pop_back();

不知道为什么我在上面的std::list 中选择了front 而不是back。我想这是感觉它更快。但实际上两者都是恒定的时间:)。无论如何将其包装成一个函数并玩得开心:

template<typename Container>
void delete_them(Container& c) { while(!c.empty()) delete c.back(), c.pop_back(); }

【讨论】:

  • 技术上是正确的,但是如果你使用更常见的大括号和缩进约定,它会变得更长。
  • 从左到右阅读:当 foo 不为空时,删除 foo 的前面,并弹出 foo 的前面 :p 换行只会妨碍:/
  • 我建议不要使用逗号(序列)运算符。太多的 C++ 开发人员不知道它是做什么的,并且会将其误认为是分号。
  • @Johannes Schaub - 是否有必要调用删除。调用 pop_back() 还不够吗?当我阅读 pop_back 的作用时,我发现它还调用了已删除元素的析构函数。 - cplusplus.com/reference/stl/vector/pop_back.
  • @EternalLearner 调用析构函数不会释放内存。您仍然需要在指针上调用delete,以便释放内存。
【解决方案2】:

既然我们在这里挑战...“最短的 C++ 块”

static bool deleteAll( Foo * theElement ) { delete theElement; return true; }

foo_list . remove_if ( deleteAll );

我认为我们可以相信提出 STL 的人拥有高效的算法。为什么要重新发明轮子?

【讨论】:

  • 我喜欢。我写了一个函数模板,但是 remove_if 的想法很好。
  • 是我自己还是当谓词有副作用时感觉真的不对?
  • C++ 11 和 C++ 14 允许您使用 lamdas 以更容易阅读。代码变为:foo_list.remove_if([](Foo *theElement){delete theElement; return true;});
【解决方案3】:
for(list<Foo*>::const_iterator it = foo_list.begin(); it != foo_list.end(); ++it)
{
    delete *it;
} 
foo_list.clear();

【讨论】:

  • 赞成,因为它表现良好并且很短。我添加了你的答案的修改版本,它更短一些(但依赖于 C++11)。
  • 问题没有指定 const 列表。为什么要使用 const 迭代器?
  • @SRINI794 我使用 const 迭代器来最大限度地提高代码的可用性,并且因为我不打算在迭代期间更改列表,所以我不需要可变迭代器。跨度>
  • @DouglasLeeder 不想小气,但通常迭代器的增量是通过:++it 而不是 it++。总之,不影响解的正确性。
【解决方案4】:

如果你允许 C++11,你可以做一个非常简短的 Douglas Leeder 的回答:

for(auto &it:foo_list) delete it; foo_list.clear();

【讨论】:

  • 这实际上不是一个好主意,原因很简单,因为您循环了 2n 次。一次循环遍历每个元素并删除内存,然后再次循环遍历列表以清除列表并将列表恢复为零。您想要做的是使用上面看到的 Mr.Ree 或 Johannees Schaub 的答案之类的方法。两者都在一个大小为 n 的循环中执行删除内存和减小列表大小后遍历列表。
【解决方案5】:

依赖容器外的代码来删除你的指针真的很危险。例如,当容器因抛出异常而销毁时会发生什么?

我知道你说过你不喜欢 boost,但请考虑boost pointer containers

【讨论】:

  • 其中一个陷阱是,反常的是,STL 允许几个重要的迭代器操作抛出异常。这使得许多通过容器使用迭代的“明显”方法不安全。见stackoverflow.com/questions/7902452/…
  • @YogeshArora 我也是,但这并不意味着这是一个有效的答案。
【解决方案6】:
template< typename T >
struct delete_ptr : public std::unary_function<T,bool>
{
   bool operator()(T*pT) const { delete pT; return true; }
};

std::for_each(foo_list.begin(), foo_list.end(), delete_ptr<Foo>());

【讨论】:

    【解决方案7】:

    为了简洁起见,我不确定函子方法是否会胜出。

    for( list<Foo*>::iterator i = foo_list.begin(); i != foo_list.end(); ++i )
        delete *i;
    

    不过,我通常建议不要这样做。通常,将指针包装在智能指针中或使用专门的指针容器会更加健壮。有很多方法可以从列表中删除项目(eraseclear 的各种风格、列表的破坏、通过迭代器分配到列表中,等等)。你能保证全部抓到吗?

    【讨论】:

    • 函子方法可能不会因为简洁而获胜,但这并不是什么大不了的。通过使用仿函数,您可以避免编写自己的 for 循环,这是软件中许多缺陷的根源。
    • 专门问的关于“最短”的问题。我不知道手动循环是缺陷的主要来源,你能提供参考吗?如果我的团队中的某个成员在编写无错误的 for 循环时遇到问题,我宁愿不要让他在仿函数解决方案上松懈。
    • 请求是“安全清理”容器。但是该代码的安全性依赖于几种不引发异常的方法:begin()end()iterator::operator!=iterator::operator*iterator::operator++。令人惊讶的是,这种依赖是不安全的:stackoverflow.com/questions/7902452/…
    【解决方案8】:

    当您的列表使用 RAII 超出范围或调用 list::clear() 时,以下 hack 会删除指针。

    template <typename T>
    class Deleter {
    public:
      Deleter(T* pointer) : pointer_(pointer) { }
      Deleter(const Deleter& deleter) {
        Deleter* d = const_cast<Deleter*>(&deleter);
        pointer_ = d->pointer_;
        d->pointer_ = 0;
      }
      ~Deleter() { delete pointer_; }
      T* pointer_;
    };
    

    例子:

    std::list<Deleter<Foo> > foo_list;
    foo_list.push_back(new Foo());
    foo_list.clear();
    

    【讨论】:

    • 我喜欢这样!它有点像 shared_ptr 但仅适用于容器,它是如此的简约和优雅。前几天我在看 boost 智能指针,它超过 200K - 难以理解 - 源代码!
    • 这真是太棒了!如果你重载指针取消引用 (*) 和成员选择 (->) 运算符,你可以让它完全透明,我会想到。
    【解决方案9】:

    至少对于一个列表,迭代和删除,然后在最后调用 clear 有点无效,因为它涉及遍历列表两次,而实际上你只需要执行一次。这里有一个更好的方法:

    for (list<Foo*>::iterator i = foo_list.begin(), e = foo_list.end(); i != e; )
    {
        list<Foo*>::iterator tmp(i++);
        delete *tmp;
        foo_list.erase(tmp);
    }
    

    也就是说,您的编译器可能足够聪明,可以循环组合两者,这取决于 list::clear 的实现方式。

    【讨论】:

    • +1 因为稳定性解决方法。我认为 c++ 的一个大问题是这段代码已经更复杂了,因为在普通的旧 C 中进行了这样的列表操作。
    【解决方案10】:

    其实我相信STD库以allocator class的形式提供了一种直接管理内存的方法

    您可以扩展基本分配器的 deallocate() 方法以自动删除任何容器的成员。

    我/认为/这是它的目的。

    【讨论】:

      【解决方案11】:
      for(list<Foo*>::const_iterator it = foo_list.begin(); it != foo_list.end(); it++)
      {
          delete *it;
      } 
      foo_list.clear();
      

      您不想这样做有一个小原因 - 您实际上是在对列表进行两次迭代。

      std::list::clear 的复杂度是线性的;它在循环中一次删除和销毁一个元素。

      考虑到上述情况,我认为最简单易读的解决方案是:

      while(!foo_list.empty())
      {
          delete foo_list.front();
          foo_list.pop_front();
      }
      

      【讨论】:

        【解决方案12】:

        C++11 起:

        std::vector<Type*> v;
        ...
        std::for_each(v.begin(), v.end(), std::default_delete<Type>());
        

        或者,如果您正在编写模板代码并希望避免指定具体类型:

        std::for_each(v.begin(), v.end(),
            std::default_delete<std::remove_pointer<decltype(v)::value_type>::type>());
        

        其中(C++14 起)可以缩写为:

        std::for_each(v.begin(), v.end(),
            std::default_delete<std::remove_pointer_t<decltype(v)::value_type>>());
        

        【讨论】:

          【解决方案13】:
          void remove(Foo* foo) { delete foo; }
          ....
          for_each( foo_list.begin(), foo_list.end(), remove );
          

          【讨论】:

          • 您知道delete foo 已经检查了foo 是否为nullptr,是吗?
          • @ChristianRau 我现在这样做。谢谢
          【解决方案14】:
          for (list<Foo*>::const_iterator i = foo_list.begin(), e = foo_list.end(); i != e; ++i)
              delete *i;
          foo_list.clear();
          

          【讨论】:

            【解决方案15】:

            这似乎是最干净的 imo,但您的 c++ 版本必须支持这种类型的迭代(我相信包括或在 c++0x 之前的任何东西都可以工作):

            for (Object *i : container) delete i;    
            container.clear();
            

            【讨论】:

              猜你喜欢
              • 2017-11-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多