正在写的一段程序,需要将指针存入一个数组。出于简洁的考虑,不想用智能指针——若要使用智能指针,则必须再给智能指针加上一层包装,以屏蔽掉穿透性的取址操作符(见ATL::CAdapt)。
因此,析构元素的操作就不得不自已动手了——erase前,得先对要删除的元素手动delete。
std::for_each(objects.begin(), objects.end(), …);
在清空数组时,需要对每个元素调用delete,很自然写出for_each时,问题来了,第三个参数传什么?stl提供了类似C#委托的函数包装对象,可以把全局delete关键词封装为函数对象吗?为此,特意百度了一下。结果还真有,像下面这样:
std::for_each(objects.begin(), objects.end(), ptr_fun(operator delete));
不过,讨论者也说了,这个使用可以编译通过,但是只能释放内存,不能调用析构函数。有人提出了一些理由,诸如对迭代器的解引用,以及operator delete与delete operator有差别等等,以及thinking in C++中的解释。大伙的讨论让我看得很晕,但真正的理由,在我看来只有一个,那就是类型信息的缺失。
我们知道,delete执行两个操作:
1. 调用对象的析构函数。
2. 将对象内存归还堆。
要执行第一步操作,就必须知道对象的类型。delete如何知道对象是什么类型?这只能通过调用处的代码上下文与多态支持,而不可能是运行时类型识别——最初的C++并不支持运行时类型识别,而且,就算支持,C++对效率的一贯追求也不允许在delete时使用运行时类型识别。
因此,在调用delete时,必须传递目标类型或其基类的指针,当传递基类指针时,要求该类族必须使用虚拟析构函数。C++类的自定义delete操作符以void*作为参数,这似乎误导了一些人。很显然的是,delete也需要知道对象的具体类型,才能将调用转交给该类型的自定义delete。有了delete的保证,自定义delete操作符并不需要检查它的参数类型。而且作为一个类型成员,它也不会不知道所属的类型。因此,自定义delete以void*为参数并不代表delete不需要类型信息。或许C++要求语法如此只是为了保证函数签名的一致?
假如我们要自己实现一个delete函数,这个函数只能是一个模板函数,就像std::_Destroy一样。
template<typename T>
void Delete(T* pObj) { pObj->~T(); free(pObj); }
对于开头处提到的ptr_fun(operator delete),这里delete并没有调用上下文,于是它退化成了一个类似void delete(void*)的函数。假若delete是一个模板函数,那么我们可以这么写:
ptr_fun(operator delete<CMyClass>)
但是很遗憾,没有这样的语法支持。
解决这个问题的唯一办法就是,写一个模板函数,接收模板类型的指针类型,然后在函数内部调用delete。这个模板函数可以传递给ptr_fun。有两点小要求:
1. 这个模板函数不能返回void,它必须有返回值,原因请看ptr_fun生成对象的operator()——该操作符会用目标函数的返回值来调用return。而编译器不支持return void这样的语法。
2. 模板函数必须在调用ptr_fun之前显式实例化,否则将无法链接成功。
class TypeA { public: ~TypeA() { cout<<"destruct"<<endl; } }; template<typename T> int Delete(T* obj) { delete obj; return 1; } template int Delete(TypeA*); int mainPtrFunDelete() { TypeA* obj = new TypeA(); //std::ptr_fun(::delete<TypeA>)(obj); std::ptr_fun(Delete<TypeA>)(obj); return 1; }