C++通用删除器设计

C++通用删除器设计

版本:0.1

最后修改:2010-11-15

撰写:李现民

概述

很久以前,我写过一篇短文讨论如何在C++项目中避免使用delete的设想,基本方法是使用域(scope)对象或std::auto_ptr代替。尽管当时已经讨论在所有可能的情况,但后面在实际项目实施中发现效果并不好。原因是方面的,比如在使用std::auto_ptr时会存在以下不得因素:

  1. 可能的额外开销外(其实很小);

  2. 你需要时刻小心对象所有权的问题。尽管可能只需要稍微注意一下就可以了,但似乎没有任何程序员喜欢过提心吊胆的日子;

  3. 你不能在容器(比如std::vector)中存储std::auto_ptr对象;

基于以上原因,类似于Text*pText=newText;这种直接在堆上申请内存的方式还是在代码得到了大量应用。而接下来就是如何安全、有效的回收这些内存的问题,这也正是本文所讨论的话题。

回收单个堆对象

//delete a object pointer and reset it

template<classT>voiddelete_null(T*&p)

{

//check if T is incomplete type, if it is, the compiler will report anerror

typedefchartype_must_be_complete[sizeof(T)?1: -1 ];

(void)sizeof(type_must_be_complete);

//delete the pointer and reset it

deletep;

p=NULL;

}

这是一个模板函数,它主要有三个作用:

第一个作用是检查被删除对象的类型完整性。这通常无法引起人们的重视,但在某些情况下可能会导致未定义行为,比如以下代码:

TextpText=newText;

voidpData=pText;

deletepData;

Text类对象pText被转换成了拥有void对象pData,并对pData调用了delete删除操作。在这种情况下编译器的行为是未知的,但至少有一点:由于编译器无法推导pData的原始类型,因此无法调用对象的析构函数。

//check if T is incomplete type, if it is, the compiler will report anerror

typedefchartype_must_be_complete[sizeof(T)?1: -1 ];

(void)sizeof(type_must_be_complete);

这两句代码可以检查被删除对象的类型完整性。其效果发生在编译期,如果对类型不完整的对象调用delete_null删除操作,将引起编译错误。它没有运行期开销,因此使用delete_null带来的安全性实际上免费的。

更加详细的解释可以参考boost库中的checked_delete.hpp

delete_null的第二个作用是回收堆对象,这没有什么可说的。

delete_null的第三个作用是将对象指针设置为NULL这主要是为了应对指针有效性检查,属于常规手段。

另外,注意到delete_null被设计为一个模板函数,在发布版本(Release)中,它将以内联代码(inline)的形式存在,因此不会有运用期函数调用开销

回收容器中的堆对象

//delete container (std::vector, std::list) items and reset them toNULL

template<typenameInputIterator>voiddelete_null(InputIteratorfirst,InputIteratorlast)

{

while(last!=first)

{

delete_null(first);

++first;

}

}



//delete functor, used for iterative delete

structdeleter

{

template<typenameT>voidoperator()(T
&p)

{

delete_null(p);

}

};

这段代码分为两部分:一个同样叫delete_null的模板函数与一个名为deleter的仿函数。

先来看第一部分,它同样叫delete_null,与前面介绍的那个版本所不同的是它接受一对迭代器作用输入条件,其作用是回收[first,last)范围内所有堆对象。与std::for_each等很多STL标准算法类似,该函数可以同时应用于普通数组或存储单值的标准容器(包括std::vector,std::list, std::set等,不包含std::map)。

第二部分比较有意思:它是一个仿函数。它可以在一定程度上代替delete_null(first,last)以下代码展示了分别使用这两种方式回收容器中的堆对象的方法

typedefstd::vector<Text*>TextPack;

TextPackuTexts1,uTexts2;

constintdatasize=100;

for(inti=0;i<datasize;++i)

{

uTexts1.push_back(newText);

uTexts2.push_back(newText);

}



//
使用delete_null

delete_null(uTexts1.begin(),uTexts1.end());

//使用deleter

std::for_each(uTexts2.begin(),uTexts2.end(),deleter());

可以看到前者稍微简洁一些(包括最终的汇编代码),那么问题来了:为什么还需要代码量更大一些的deleter仿函数?

理由是:并不是所有存储堆对象的集合都是直接存储对象指针的。比如可以将指针存储在std::map中“值”部分,甚至有些自定义集合只提供了遍历函数(类似于std::for_each),但并不公开迭代器接口。在这些情况下,我们就可以使用deleter仿函数进行堆对象回收。

转载于http://www.cppblog.com/xmli/archive/2010/11/15/133653.html
原文链接: https://www.cnblogs.com/shengzd/archive/2010/11/16/1878915.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/17425

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午6:00
下一篇 2023年2月7日 下午6:00

相关推荐