让vector析构时不释放内存 - lhf 的窝
慎用7za.exe的-r参数
虚拟表格显示控件

让vector析构时不释放内存

LiHengFeng posted @ 2008年4月11日 03:21 in 编程 , 4340 阅读
本blog所有文章,除声明了作者外,均为原创。欢迎转载,转载请注明作者。

让vector析构时不释放内存

摘要:通过指定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);}
 };
}

 


 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter