65.9K
CodeProject 正在变化。 阅读更多。
Home

删除 STL 序列容器中指针的 Functor

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.48/5 (16投票s)

2004年3月22日

BSD

4分钟阅读

viewsIcon

75203

downloadIcon

679

一个基于策略的删除 Functor,可与 for_each 函数一起使用。

引言

STL 容器的一个优点是它们会自动为我们处理内存。通过使用 vector 或 deque 等标准顺序容器,而不是直接处理 new[]delete[] 运算符,我们可以避免内存泄漏的可能性,并使我们的代码具有异常安全性。例如,如果在函数中需要一个整数的动态数组,如果我们使用 std::vector<int> 而不是 int*,我们将不必担心在退出函数之前调用 delete 来释放内存。

vector<int> vectorArray (30); // the memory will be automatically freed
...
int* oldStyleArray = new int[30]; // be sure to call delete[] oldStyleArray;

然而,如果我们把指针放入标准容器中,事情就会变得更加复杂。

struct C
  {
  char data_[50];
  };
  ...
  vector <C*> dataArray;
for (int i = 0; i < 20 ; ++i)
  dataArray.push_back(new C());

dataArray 退出作用域后,只有指针本身占用的内存会被释放,但对象本身将保持未删除状态,除非我们这样做:

for (vector <C*>::iterator it = dataArray.begin(), 
  it != dataArray.end(); ++it)
  delete *it;

dataArray 退出作用域之前。

处理此问题的最佳方法是永远不要将原始指针放入容器中,而是使用智能指针,例如 boost::shared_ptr [1] 或 Loki::SmartPtr [2]。

vector <boost::shared_ptr<C> > dataArray;
for (int i = 0; i < 20 ; ++i)
  dataArray.push_back(boost::shared_ptr<C> (new C()));

现在,我们无需担心释放对象。

但是,在某些情况下,我们仍然希望在 STL 顺序容器中保留原始指针,在这种情况下,我们可以使用一种比手动编写循环更简单的方法来清理内存。一种常见的解决方案是编写一个 Functor,使我们能够使用诸如 for_each [3] 之类的 STL 算法。

for_each(dataArray.begin(), dataArray.end(), del_fun<C>());

这种方法更好,但仍有许多不足之处。首先,它总是调用 operator delete,这对于使用 new[]malloc 创建的对象的情况并不适用。此外,在某些情况下,我们可能希望在删除指针后将其清零。

为了解决这些问题,我实现了一个基于策略的 Functor free_ptr

free_ptr 的设计

在使用 Functor 删除对象时,我们面临两个独立的选择:

  1. 使用哪个函数来删除对象?
  2. 删除后是否将指针清零?

为了让用户做出选择,我使用了两个单独的策略:CleanUpPolicyZeroPtrPolicy

template <typename T, class CleanUpPolicy = DeleteSingleObject, class 
ZeroPtrPolicy = DontZeroPtr>
struct free_ptr : std::unary_function <T*, void>
  {
  void operator()(T*& ptr)
    {
    CleanUpPolicy::Destroy(ptr);
    ZeroPtrPolicy::Zero(ptr);
    }
};

得益于基于策略的设计,free_ptr 非常易于使用,同时又高度可定制。对于最常见的情况(使用 new 创建的单个对象,无需清零指针),可以这样使用:

for_each(dataArray.begin(), dataArray.end(), free_ptr<C>());

如果对象是使用 new[] 创建的,并且我们需要在删除后清零指针,我们可以这样写:

for_each(dataArray.begin(), dataArray.end(), free_ptr<C, DeleteArray, 
ZeroPtr>());

对于 CleanUpPolicy,我们有以下选择:

  1. DeleteSingleObject(默认)– 对每个指针调用 delete
  2. DeleteArray – 对每个指针调用 delete[]
  3. Free – 对每个指针调用 free()

对于 ZeroPtrPolicy,我们有两个选择:

  1. DontZeroPtr(默认)– 不执行任何操作。
  2. ZeroPtr - 将每个指针设置为零。

在理想情况下,我们甚至不需要 CleanUpPolicy – 如果指向单个对象的指针与指向对象数组的指针是不同的类型,我们可以使用一些模板元编程在编译时识别正确的场景。然而,显然情况并非如此,我尝试找出一些运行时机制来做到这一点。我惨败而归:如果我们只有一个指向内存的指针,似乎没有好的方法来区分内存是使用 newnew[] 还是 malloc 分配的。我甚至尝试了一些非便携的技巧,例如使用 _memsize 来确定分配内存块的大小,但是,即使我们知道分配的内存量,它仍然不一定能显示指针指向的对象数量。因此,我能想到的最好的方法是 CleanUpPolicy。这种解决方案的一个好处是,在某些自定义内存管理方案的情况下,编写另一个 CleanUpPolicy 类并像使用我提供的类一样容易地使用它会非常简单。

我可能应该补充一点,使用 free_ptr 无法保护您免受某些糟糕的编程实践。例如,如果您这样做:

vector <C*> dataArray;
dataArray.push_back(new C()); //single object

dataArray.push_back(new C[10]); //array of objects

dataArray.push_back((C*)malloc(sizeof(C))); // malloc-ed object

那么 free_ptr 将无法帮助您。正如我所说,不幸的是,前面的示例会编译,但这只是 C 的另一个遗留问题,使得 C++ 不够完美。

结论

我希望这篇小文章能以两种方式为您带来帮助:它展示了策略设计,这是一种用于开发 C++ 库的强大方法,并且它还为您提供了一个很酷的小代码片段,您可以在日常编程任务中找到它的用途。

参考文献

© . All rights reserved.