关于STL中stack的实现的讨论

    技术2022-05-11  61

    摘要: 文章讨论了为什么大多数STL的stack的实现中,对于内部的容器默认选择deque容器;并且给出了自己的几个不同想法实现的stack;并进行了简单的性能比较测试;(文章最后给出了一个性能、特性都无懈可击的stack的实现!)

      文章来源于abp论坛中的一篇讨论帖子: http://bbs.allaboutprogram.com/viewtopic.php?t=1026这是自己开始接触泛型和STL时形成的一篇讨论;文章中借用了Elminster,papercrane,Innocentius,PolyRandom等人的部分观点

    1:为什么大多数STL的stack的实现中,对于内部的容器默认选择deque容器?而不是vector?      STL中,stack对内部使用容器的函数调用主要有:push_back,back,pop_back等,也就是顺序容器都满足要求(包括vector,deque,list)。很多人应该和我一样,在STL之前看到的stack实现都是以动态数组来(甚至静态数组)实现为主,也就是接近于使用vector方案;那为什么STL偏偏选择deque呢!?    我的分析:    a.用vector实现中(push_back动作为分期摊还常数时间),如果发生容器的大小改变时,将可能产生一个大动作(申请空间,拷贝构造,释放原来的元素和空间,该动作成线性复杂度) 而且vector的很多实现版本中,容器在任何情况下都从不缩减已经申请的空间容量(swap技巧除外);     b.用deque实现时,容器的大小改变时(数据量较大),动作比vector就小多了(常数复杂度),并且当容器的大小变小时,还可以适当减小容量;但push_back 的逻辑相对vector复杂一点;    c.用list实现时,不用考虑空间容量变化;但每次的压入弹出开销(内存时间)较大,但很平稳;那么,经过分析,在不同的应用场合,为stack选择不同的内部容器是很有必要的;如果对stack有性能上的要求,就应该考虑这一点(甚至重新写一个最适应问题要求的stack); 比如:要求有最快的平均访问速度,而且大概的容量要求也清楚(比较衡定),那么,使用vector是个不错的选择 要求每次的访问时间平稳,而不在乎平均访问时间时,那么,可以考虑使用list;所以,库默认的deque是个不错的选择,它介于vector和list之间,并且很好的综合了两者的优势;另papercrane:“oncrete policy deque相对于stack来说就像傻瓜机,乱用也不会有什么太大的问题。如你所说的平均时间和最差时间的要求,我觉得就好像hash map和tree map的性能差别一样。 ”

        (提示:文章后面还有两种想进一步融合这三种方式各自优势的stack的实现,特别是最后那个实现也许推翻了这里的表面上得到的看法);

    2.自己也来写一个stack;  由于看到VC6中实现的太差,所以自己简单写了一个stack模版实现,性质比较接近于stack<T,vector<T> >, 代码如下:

    template<class T>  class  mystack // 测试用 // 没有考虑异常时的rollback语义 //另: Elminster指出“使用 count 不是一个好主意。保存一个指向“下一个位置”的指针应该会效率更高,而且也更易读”; public :     typedef T               value_type;     typedef unsigned int       size_type;     mystack():_lenght(0),_count(0 ) {}     ~ mystack()    {       if (_lenght!=0 )       {          for (int i=0;i<_count;++ i)          {             ((T*)(&_vData[i*sizeof(T)]))->T::~ T();          }       }   }     bool empty() const           { return (0== _count); }     size_type size() const        { return  _count; }     value_type&  top()       { return *(T*)(&_vData[(_count-1)*sizeof (T)]); }     const value_type& top() const        { return *(T*)(&_vData[(_count-1)*sizeof (T)]); }     void push(const value_type&  x)    {       if (_count>= _lenght)       {          _resize();       }       new ((T*)(&_vData[_count*sizeof (T)])) T(x) ;       ++ _count;    }     void  pop()    {       -- _count;       ((T*)(&_vData[_count*sizeof(T)]))->T::~ T();    } protected :    std::vector<unsigned char>    _vData;    size_type      _lenght;    size_type      _count;    void  _resize()    {       if (0== _lenght)       {          _lenght=32 ;          _vData.resize(_lenght*sizeof (T));       }       else        {          _lenght*=2 ;          std::vector<unsigned char>  new_vData;          new_vData.resize(sizeof(T)* _lenght);          for (int i=0;i<_count;++ i)          {             T& x=*(T*)(_vData.begin()+i*sizeof (T));             new ((T*)(new_vData.begin()+i*sizeof (T))) T(x);             (&x)->T::~ T();          }          _vData.swap(new_vData);       }    } }; 

    测试环境:VC6,赛扬1G,256M内存 测试代码:(不好意思,代码风格被VC的环境影响太久,想改变这种风格ing) 另: Elminster指出测试代码里面,TestStack("stack_deque_T0 : ",stack_deque_T0()) 这个做法也不太妥当。TestStack 的第二个参数是 stackT&,把一个临时对象绑在非常量引用上可能会带来问题。

     typedef  int  Test0_T; // 简单 POD 类型  class  Test1_T    // 较复杂的类  public :     char  _c;     double  _d;     int   _i;     char *  _p;    Test1_T():_c(),_d(),_i(),_p( new   char [ 20 ])       {}    Test1_T( const  Test1_T &  x)       :_c(),_d(),_i(),_p( new   char [ 20 ])       { ( * this ). operator   = (x);  }     ~ Test1_T(){ delete[] _p; }    Test1_T &   operator   = ( const  Test1_T &  x)    {       _c = x._c; _d = x._d; _i = x._i;        for  ( int  i = 0 ;i < 20 ; ++ i)          _p[i] = x._p[i];        return   * this ;    } }; __declspec( naked ) __int64 CPUCycleCounter() // 获取当前CPU周期计数(CPU周期数) { __asm {  RDTSC     // 0F 31   // eax,edx   ret }}template < class  stackT >   int  testProc(stackT &  s, int  Count) {    typename stackT::value_type vl = stackT::value_type();    typename stackT::value_type vx;    __int64   t0 = ::CPUCycleCounter(); // 返回CPU启动以来运行的周期数      for  ( int  c = 0 ;c < 100 ; ++ c)    {        int  i;        for  (i = 0 ;i < Count; ++ i)          s.push(vl);        for  (i = 0 ;i < Count; ++ i)          vx = s.top();        for  (i = 0 ;i < Count; ++ i)          s.pop();    }    __int64   t1 = ::CPUCycleCounter();     return    int ((t1 - t0) / 100 ); } template < class  stackT >  CString TestStack(PCSTR lab,stackT &  s) {     int    t0 = testProc(s, 10 );     int    t1 = testProc(s, 1000 );     int    t2 = testProc(s, 100000 );    CString str;    str.Format( " d,d,d " ,t0,t1,t2);    str += char ( 13 );str += char ( 10 );     return  lab + str; }  void  CSTACKTESTDlg::OnBUTTONTest() {     //  TODO: Add your control notification handler code here      using   namespace  std;    typedef stack < Test0_T,deque < Test0_T >   >    stack_deque_T0;    typedef stack < Test1_T,deque < Test1_T >   >    stack_deque_T1;    typedef stack < Test0_T,vector < Test0_T >   >    stack_vector_T0;    typedef stack < Test1_T,vector < Test1_T >   >    stack_vector_T1;    typedef stack < Test0_T,list < Test0_T >   >    stack_list_T0;    typedef stack < Test1_T,list < Test1_T >   >    stack_list_T1;    typedef mystack < Test0_T >             mystack_T0;    typedef mystack < Test1_T >             mystack_T1;    CString str;    str += TestStack( " stack_deque_T0 :  " ,stack_deque_T0());    str += TestStack( " stack_vector_T0:  " ,stack_vector_T0());    str += TestStack( " stack_list_T0  :  " ,stack_list_T0());    str += TestStack( " mystack_T0     :  " ,mystack_T0());    str += char ( 13 );str += char ( 10 );    str += TestStack( " stack_deque_T1 :  " ,stack_deque_T1());    str += TestStack( " stack_vector_T1:  " ,stack_vector_T1());    str += TestStack( " stack_list_T1  :  " ,stack_list_T1());    str += TestStack( " mystack_T1     :  " ,mystack_T1());     this -> m_str = str; // ::MessageBox(0,str,"",0);     UpdateData(FALSE); }

    测试结果:(不同使用环境下的测试情况可能不同,该数据仅作参考) (另:测试中使用的STL是VC6自带的)

    (计时单位:万CPU周期) N: 10 1000 100000 stack_deque_T0  : 2695,  65621,  12191532 stack_vector_T0 :  579,  47750,   9169942 stack_list_T0   : 6028, 957515, 167660924 mystack_T0      :  230,  13766,   2550403 (效果很好嘛)

    stack_deque_T1 : 10699, 1168983, 270182336 stack_vector_T1:  8043, 1247187, 250648378 stack_list_T1  : 13043, 1988924, 424801657 mystack_T1     :  6796, 1240879, 252690706(对于复杂对象,与stack_vector_T1的差不多)

    Elminster给出了他的测试结果:(计时单位:tick count) :stack_deque_T0  : 0 156 12969 stack_vector_T0 : 0  63  7562 mystack_T0      : 0  47  6766

    stack_deque_T1  : 16 500 57765 stack_vector_T1 :  0 813 91843 mystack_T1      :  0 734 85469

    环境是 amd athlon 1600+, win2k sp4, 256M ddr, vs.net 2003,最大速度优化。

    Elminster:“结论比较有趣。对于拷贝动作比较轻量级的 T0,你的方案比 deque 和 vector 都快,但与 vector 相差不大。此时 deque 的性能落的比较后面,原因应该是 deque 的 push_back 的逻辑相对复杂(我看了看)。对于拷贝动作比较重的 T1 ,你的方案和 vector 反而要比 deque 慢。这里的原因应该是 resize 的时候拷贝的开销太重。其实我认为对于 stack 的行为模式,类似 deque 的存储结构会比较好,因为空间完全不需要连续,像vector 那样需要拷贝的 resize 是毫无必要的。你自己实现一个简洁的 deque style 数据结构,相信可以把stack 的性能再提升一个台阶。”

    /对上面自己写的stack做了些改进:测试环境:VC6,XP,赛扬1G,256M内存 测试结果:

     template < class  T >   class  mystack {  public :     enum { type_sizes = sizeof (T) };     typedef T               value_type;     typedef unsigned  int       size_type;     mystack():_begin( 0 ),_end( 0 ),_last( 0 ) {}      ~ mystack()    {        if  (size() > 0 )       {           for  (T *  i = _begin;i < _end; ++ i)             i -> T:: ~ T();       }        if  (_begin != 0 ) delete[] (unsigned  char * )_begin;   }      bool  empty()  const           {  return  ( 0 == size()); }     size_type size()  const        {  return  (_end - _begin); }     value_type &  top()       {  return   * (_end - 1 ); }      const  value_type &  top()  const        {  return   * (_end - 1 ); }      void  push( const  value_type &  x)    {        if  (_end < _last)       {           new  (_end) T(x) ;           ++ _end;       }        else        {          _resize();           new  (_end) T(x);           ++ _end;       }    }      void  pop()    {        -- _end;       _end -> T:: ~ T();    }  protected :    T *             _begin;    T *             _end;    T *             _last;     void  _resize()    {        if  ( 0 == _begin)       {           const  unsigned  int  lenght = 32 ;          _begin = (T * )(  new  unsigned  char [lenght * type_sizes]);          _end   = _begin;          _last  = _begin + lenght;       }        else        {          unsigned  int  old_size = size();          unsigned  int  lenght = (old_size << 1 );          T *  _pNewData = (T * )(  new  unsigned  char [lenght * type_sizes]);            int  i = 0 ;           try           {              for  (;i < ( int )old_size; ++ i)                 new  (_pNewData  +  i) T( * (_begin + i));          }           catch (...) // rollback语义           {              for  ( int  r = 0 ;r < i; ++ r)                (_pNewData  +  r) -> T:: ~ T();             delete[] (unsigned  char * )_pNewData;              throw ;          }           for  ( int  j = 0 ;j < ( int )old_size; ++ j)             (_begin + j) -> T:: ~ T();          T *  old_begin = _begin;          _begin = _pNewData;          _end   = _begin + old_size;          _last  = _begin + lenght;          delete[] (unsigned  char * )old_begin;       }    } };

    stack_deque_T0  : 2704,   65916,  11726143 stack_vector_T0 :  581,   45713,   9192746 stack_list_T0   : 5941, 1010118, 181184020 mystack_T0      :  172,   12210,   2590757

    stack_deque_T1  : 10587, 1962722, 255087740 stack_vector_T1 :  8145, 1237245, 243123956 stack_list_T1   : 13128, 2005212, 398256062 mystack_T1      :  7127, 1173165, 251515265

    测试环境:VS.net,XP,赛扬1G,256M内存 stack_deque_T0  :  525,   33407,   6468531 stack_vector_T0 :  574,   34710,   5236028 stack_list_T0   : 4753, 1060449, 165353094 mystack_T0      :  160,   10200,   2290310

    stack_deque_T1  :  7470,  963957, 266757848 stack_vector_T1 :  8412, 1275307, 278246615 stack_list_T1   : 12001, 1993198, 475480319 mystack_T1      :  7317, 1209379, 262822216

    对比VC6, vs.net的 deque的性能好像提高了不少,但和vector一样还有优化的空间

    3.利用x86的虚拟空间地址原理来实现stack    deque的实现中一般内部维护一个动态指针数组,这些指针指向数据块(每块保存多个元素),这些块在内存中是不连续的,然而deque利用软件的方式提供了一个对外的线性访问的假象;     看一下现在的x86CPU,也有一个机制和这很像,即:保护的虚拟地址内存模型; 它把不连续的物理内存映射为一维线性内存模型,由于是硬件支持的映射,所以这种机制不会损失任何性能(deque、stack、vector等的实现也可以利用这一点)    实现stack时,可以先开辟很大一块内存空间(足够),但不一次全部提交,开始只提交很少 的一部分物理页面,当需要增大容器容量时,只需要再提交部分物理页面给它,当需要减少容器容量时 可以收回部分页面;这个方案很像是deque的实现,但它可以获得vector式的线性内存访问能力和性能;     当然这会占用较大量的虚拟内存地址空间,这个方案也可能和具体平台相关,但不会浪费真正的物理内存空间(如果是64位CPU,那么虚拟内存地址空间的浪费就可以不用考虑了:))

    新的容器ExStack,它同时具有vector的线性访问能力,和deque的内存管理方式,所以我期待它具有这两者的性质: 

    class  TAlloc // 移植时 在不同的平台下需要改变的部分  public :     static   void *  reserve(unsigned  int  size)          // 申请保留虚拟空间地址     {        return  ::VirtualAlloc( 0 ,size,MEM_RESERVE,PAGE_READWRITE);    }     static   bool  commit( void *  pbase,unsigned  int  offset,unsigned  int  size)    // 提交物理空间     {        return   0 != ::VirtualAlloc(((BYTE * )pbase) + offset,size,MEM_COMMIT,PAGE_READWRITE);    }     static   bool  free( void *  pbase)  // 解除提交的物理空间,并释放申请的虚拟空间地址     {        return   0 != ::VirtualFree(pbase, 0 ,MEM_RELEASE);    } };  template < class  T >   class  ExStack {  public :     enum { type_sizes = sizeof (T) };    typedef T               value_type;     typedef unsigned  int       size_type;     ExStack():_begin( 0 ),_end( 0 ),_last( 0 ) {}      ~ ExStack()    {        if  (size() > 0 )       {           for  (T *  i = _begin;i < _end; ++ i)             i -> T:: ~ T();       }        if  (_begin != 0 ) TAlloc::free(_begin);    }      bool  empty()  const           {  return  ( 0 == size()); }     size_type size()  const        {  return  (_end - _begin); }     value_type &  top()       {  return   * (_end - 1 ); }      const  value_type &  top()  const        {  return   * (_end - 1 ); }      void  push( const  value_type &  x)    {        if  (_end < _last)       {           // std::_Construct(_end,x) ;            new  (_end) T(x) ;           ++ _end;       }        else        {          _resize();           // std::_Construct(_end,x);            new  (_end) T(x) ;           ++ _end;       }    }      void  pop()    {        // 没有实现收回物理内存的语义        -- _end;       _end -> T:: ~ T();    }  protected :    T *             _begin;    T *             _end;    T *             _last;     void  _resize()    {        if  ( 0 == _begin)       {           const  unsigned  int  lenght = 4 * 1024 ; // 4KB 边界对齐           _begin = (T * )TAlloc::reserve( 256 * 1024 * 1024 ); // 预留地址空间 256 MB           TAlloc::commit(( void * )_begin, 0 ,lenght * type_sizes);          _end   = _begin;          _last  = _begin + lenght;       }        else        {          unsigned  int  old_size = size();          unsigned  int  lenght = (old_size << 1 );          TAlloc::commit(_begin,old_size * type_sizes,(lenght - old_size)  * type_sizes);          _last  = _begin + lenght;       }    } }; //测试条件更加接近于一般使用环境, 测试也更合理 int  testProc(stackT &  s, int  Count) // 注意测试条件变了很多!!!  {     typename stackT::value_type vl = stackT::value_type();    typename stackT::value_type vx;    __int64   t0 = ::CPUCycleCounter(); // 返回CPU启动以来运行的周期数      for  ( int  c = 0 ;c < 100 ; ++ c)    {       stackT temps; //             int  i;        for  (i = 0 ;i < Count + 1 ; ++ i) // +1           temps.push(vl);        for  (i = 0 ;i < Count; ++ i)          vx = temps.top();        for  (i = 0 ;i < Count - 1 ; ++ i) // -1           temps.pop();        for  (i = 0 ;i < (Count >> 1 ); ++ i) //          temps.push(vl);    }    __int64   t1 = ::CPUCycleCounter();     return    int ((t1 - t0) / 100 ); } 测试环境:XPvc.net赛扬1GHz256MB

    测试结果:

                N  =         10       1000     100000 stack_deque_T0 :       2942,    205538,  54123660 stack_vector_T0:       4903,     66692,  21136702 stack_list_T0  :       8003,   1232967, 239859174 mystack_T0     :       1120,     20431,  12025250 ExStack_T0     :      40710,     75998,   5908666

    stack_deque_T1 :      18444,   2383833, 490387023 stack_vector_T1:      35764,   3281555,1130011712 stack_list_T1  :      19624,   2842182, 588527582 mystack_T1     :      11764,   2285440, 749906486 ExStack_T1     :      56646,   1680889, 372481178

    mystack: 在大多数情况下都很优秀,但当数据量较大并且数据类型较复杂时,性能迅速下降std::stack<T,vector<T> >: 性质与mystack一致,但VC6版实现得太差了;(这种库看了就让人生气)std::stack<T,list<T> >: 绝对速度(平均速度)没法和其他实现比,但他的优点不在这;ExStack: 初始化和销毁开销太大了些(否则ExStack在很多测试中都将领先),数据量较大时,不出意料的在简单和复杂数据类型中都领先于对手(这时才显示出综合了vector与deque的优势);但是ExStack的适用范围实在太小了,比我预期的适用范围差;

    (PolyRandom:我觉得这个ExStack不错,而且可以用的地方应该不少。)

    4. 极速stack的诞生myfast_stack

        一次尝试(前奏):   还是忍不住自己写了一个deque内存管理方式的_myfast_stack;性质接近于std::static<T,deque<T> >; 其实比我想象中简单多了,很快就实现出来了(因为不需要实现一个完整的deque),测试时各项性能也很优秀;只是在“简单数据类型、元素个数N很小”时才输给了vector内存管理方式实现的mystack! 这一点好像在意料之中。

        是否这就是极限了呢?我准备把源码和测试结果发布出来的时候,却突然有了新的想法... 

    一般deque内存管理需要用一个动态数组来保存指向数据块的指针,因为deque要求随机访问能力;但stack访问时明显没有随机访问特性的要求,所以 保存这些指针的数据结构最低需求也是满足stack接口就足够了;进一步的改进方案出来了,先用一个list来管理这些数据块,再把list的自己的数据成员与需要管理的数据空间合成放在一起(放在同一个数据块上);

    哈哈,综合性能新的明星myfast_stack诞生了; 让人不敢相信的测试结果!!!

    测试环境:Win2000,VC6,赛杨466,128M ( 自己的老古董电脑 )

                 N =         20       1000      50000 stack_deque_T0 :        434,      9355,    494481 stack_vector_T0:        509,     11770,    583668 stack_list_T0  :       1330,     82371,   7071753 mystack_T0     :        103,      3248,    323565 ExStack_T0     :       3258,      5580,    163168 myfast_stack_T0:         97,      2682,    190075

    stack_deque_T1 :       2464,    135898,  11981644 stack_vector_T1:       4042,    228555,  22766841 stack_list_T1  :       3281,    207232,  16723651 mystack_T1     :       1916,    206413,  21782916 ExStack_T1     :       5228,    129881,  10933717 myfast_stack_T1:       1983,    124296,  10399781

    实现的实质还是deque方式的,不管从那方面来看我认为它都可以将其他几个vector和list等的实现淘汰掉!

    !!!myfast_stack太恐怖了,几乎没有缺陷!!!

    ///myfast_stack源代码// // 管理内存的list  template < unsigned  int  byte_size >   class  Tdata_list // 管理myfast_stack的内存  {     struct  TNode // 节点类型     {       TNode *          pPrev;       TNode *          pNext;       unsigned  char    Data[byte_size]; // 数据空间     };  public :    Tdata_list():pNodeBegin( 0 ),pNodeCur( 0 ),_size( 0 ){}    unsigned  int  size()  const  {  return  _size; }     void  push() // 配置空间     {        if  ( 0 == pNodeBegin)       {          pNodeBegin = new  TNode;          pNodeBegin -> pPrev = 0 ;          pNodeBegin -> pNext = 0 ;          pNodeCur = pNodeBegin;       }        else   if  ( 0 != pNodeCur -> pNext) // 还有一个空余的Node        {          pNodeCur = pNodeCur -> pNext;       }        else        {          TNode *  pNodeEnd = new  TNode;          pNodeEnd -> pPrev = pNodeCur;          pNodeEnd -> pNext = 0 ;          pNodeCur -> pNext = pNodeEnd;          pNodeCur = pNodeEnd;          }        ++ _size;    }     ~ Tdata_list()    {        for  (TNode *  i = pNodeBegin;i != 0 ; )       {          TNode *  pNext = i -> pNext;          delete i;          i = pNext;       }    }    unsigned  char *  top()    {           return  pNodeCur -> Data;    }     void  pop()    {       TNode *&  pTmp = pNodeCur -> pNext;        if  (pTmp != 0 )       {          delete pTmp; // 留一个空余Node,多余的释放           pTmp = 0 ;       }       pNodeCur = pNodeCur -> pPrev;        -- _size;    }  private :    TNode *    pNodeCur;    TNode *    pNodeBegin;    unsigned  int  _size; // 用来追踪list的使用大小  };  // myfast_stack  // 注意它的实现并没有以降低stack的通用能力来提高性能  // 改进可能:1.提供专署的内存分配器,而不是默认的new/delete; (就可以和SGI中的stack对比测试了) template < class  T, bool  IsPOD = false >   class  myfast_stack {  public :     enum { type_sizes = sizeof (T), //          node_width = ( 1020 / type_sizes) + 1 // 使用这种策略,stack<T,list<T> >也没有存在必要了        };     typedef T                           value_type;     typedef unsigned  int                   size_type;    typedef Tdata_list < type_sizes * node_width >    Tbase_alloc;     myfast_stack():_node_begin( 0 ),_node_cur( 0 ),_node_last( 0 ) {}      ~ myfast_stack()    {        if  (( ! IsPOD) && (_NodeList.size() > 0 ))       {           for  (T *  i = _node_begin;i < _node_cur; ++ i)             i -> T:: ~ T();           int  nsize = ( int )_NodeList.size();           for  ( int  j = 0 ;j < (nsize - 1 ); ++ j)          {             _NodeList.pop();             _node_cur = (T * )_NodeList.top();              for  ( int  i = 0 ;i < node_width; ++ i)             {                _node_cur -> T:: ~ T();                 ++ _node_cur;             }          }       }    }      bool  empty()  const           {  return  ( 0 == size()); }     size_type size()  const        {  return  (_node_last - _node_begin) + node_width * (min(( int )_NodeList.size() - 1 , 0 )); }     value_type &  top()       {  return   * (_node_cur - 1 ); }      const  value_type &  top()  const        {  return   * (_node_cur - 1 ); }      void  push( const  value_type &  x)    {        if  (_node_cur == _node_last)       {          _ToNextNode();       }        // std::_Construct(_node_cur,x);         new  (_node_cur) T(x) ;        ++ _node_cur;    }      void  pop()    {        if  (_node_cur == _node_begin)       {          _ToPrevNode();       }        -- _node_cur;       _node_cur -> T:: ~ T();    }  protected :    T *             _node_begin;    T *             _node_cur;    T *             _node_last;    Tbase_alloc      _NodeList;     void  _ToNextNode()    {       _NodeList.push();          _node_begin = (T * )(_NodeList.top());       _node_last = _node_begin + node_width;       _node_cur = _node_begin;    }     void  _ToPrevNode()    {       _NodeList.pop();       _node_begin = (T * )(_NodeList.top());       _node_last = _node_begin + node_width;       _node_cur = _node_last;    } };
     

    最新回复(0)