摘要:通过指定allocator的办法控制vector的内存释放。
关键字:allocator vector 内存释放
经常有这样的情况,一个函数要返回一个不定长的数组。一般情况下,直接在函数里面动态分配内存就好了。不过很多时候,往往涉及到更复杂的操作,如插入/删除元素或是出栈/入栈什么的,这时使用STL的vector就是最好的选择了。但这种情况下,vector有一个不方便的地方,就是它的析构函数会释放内存。这当然是正确的行为,但是这样一来,我们的代码就变成这样了:
T * foo()
{
std::vector<T > aa;
...//使用aa
T *buf = (T*)malloc(aa.size()*sizeof(T));
memcpy(buf,&aa[0],aa.size()*sizeof(T));
return buf;
}
其中T是一个POD类型.
在函数结尾的时候,我们无法直接返回 &aa[0],虽然那就是我们要的数组。因为aa是一个类,它的析构函数在函数返回前必须被调用,函数结束后,对应的内存已经无效。所以在返回之前,要将它的内存复制一次.
当然,你可以绕过这个问题,只要将aa作为函数的参数,而不是局部变量就可以了。不过我想要的是直接的解决方法,而不是绕过去。虽然你现在也许可以绕过去,但你迟早碰到绕不过去的情况,比如若函数的规格已经确定不能更改呢?
这里要解决的问题就是"如何使vector析构时不释放内存"。看起来很奇怪的要求,但实际上有时候却是合理的。解决办法当然有,别忘了,stl有着极好的弹性。在使用vector的时候,本来是有两个参数的,上面的例子只使用了一个类型参数,第二个参数就是解决办法所在了。
vector的第二个参数是指定vector使用的内存分配类allocator。只要我们实现自己的allocator,就可以让vector按找我们的意思分配释放内存。allocator的规格可以在VC的头文件...vc98\include\xmemory里面看到。这里插一句,本来allocator的规格在stl中是有规定的,不过stl有好多版本的实现,其中有些版本就未必遵循stl的标准规则。比如vc6使用了P.J.Plauger版本,就没有完全遵循标准规格。所以我们必须搞清楚相应的规格才行。
下面就是我改写的一个allocator,为了与标准的区别开来,将它包含在名字空间lhf中。
namespace lhf
{
int MemKeep_flag = 0;
void setMemKeep(bool flag){ MemKeep_flag=flag; }
template<class _Ty>//自定义的内存管理,目的是不释放vector的内存.
class allocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty _FARQ * pointer;
typedef const _Ty _FARQ * const_pointer;
typedef _Ty _FARQ& reference;
typedef const _Ty _FARQ& const_reference;
typedef _Ty value_type;
pointer address(reference _X) const { return (&_X); }
const_pointer address(const_reference _X) const {return (&_X); }
pointer allocate(size_type _N, const void *){ return (pointer)malloc(_N*sizeof(_Ty));}
char _FARQ *_Charalloc(size_type _N){ return (char _FARQ *)malloc(_N); }
void deallocate(void _FARQ *_P, size_type){ if(!MemKeep_flag) free(_P);}
void construct(pointer _P, const _Ty& _V){ new ((void _FARQ *)_P) (_Ty)(_V); }
void destroy(pointer _P){ _P; (_P)->~_Ty(); }
_SIZT max_size() const{_SIZT _N = (_SIZT)(-1) / sizeof (_Ty); return (0 < _N ? _N : 1); }
};
}
上面的代码中,主要就是增加了一个全局变量MemKeep_flag和函数setMemKeep,来控制vector是否能释放掉内存。如果MemKeep_flag为假,则vector可以象平时一样,正常释放内存。否则,vector就不能释放内存,即使析构函数被调用了。
现在,上面的函数就可以这样使用vector.
T * foo()
{
T *buf = 0;
{
std::vector<T,lhf::allocator<T> > aa;
...//使用aa
buf = (T*)&aa[0];
lhf::setMemKeep(true);//禁止释放内存
}
lhf::setMemKeep(false);//恢复释放内存
return buf;
}
注意上面的两次setMemKeep的调用,其位置很重要(在两次调用之间必须包含析构函数的调用)。另外,将aa的定义放在一个大括号内也很重要,这决定了aa何时调用析构函数。
谈一个东西只说好处而不说坏处是可耻的,这容易让人想起政客或者商家们的推销。首先,前面已经说过,各版本的STL实现不尽相同,上面的allocator也许无法与某些stl版本搭配使用,或是可以使用,但反而大大降低了效率。其次,注意到上面的代码中使用了全局变量MemKeep_flag,我们知道,象这样使用全局变量的代码是无法经受多线程考验的。比如,一个线程调用了setMemKeep(true),在调用setMemKeep(false)之前就切换到了另外一个线程。这下好戏上演了,在后一个线程中,vector将无法释放任何内存!
再次提醒一下,如果你要在代码中使用类似的花招,千万要记住,setMemKeep(true)和setMemKeep(false)要成对调用,中间的间隔越小越好,并确保不会涉及到多线程的问题。切记,切记!
最后祝编程愉快!
最近将上面的代码移植到vs2005下,居然过不了。一看,原来allocator的 接口定义改了,shit。唉,看来完全自己实现一个allocator 是靠不住了。只好派生一个算了,下面是新的代码:
namespace lhf
{
int MemKeep_flag = 0;
void setMemKeep(bool flag){ MemKeep_flag=flag; }
template<class _Ty>//自定义的内存管理,目的是不释放vector的内存.
class allocator : public std::allocator<class _Ty>
{
public:
void deallocate(pointer _Ptr, size_type){ if(!MemKeep_flag) __super::deallocate(__Ptr);}
};
}