C++

C++ TinySTL 之vector 设计,实现与中间的坑

内容概要: c++ stl vector vector 设计 vector 实现

本文只介绍了vector 设计与实现过程中遇到的问题, 整个STL的设计与实现可以参考: C++ TinySTL 设计与实现

c++ std::vector 是一种封装了动态数组的顺序容器(std::vector is a sequence container that encapsulates dynamicsize arrays.)

一.  STL vector 总体设计

1.    内存管理

  • 方案一: 使用std::allocator分配,释放内存(标准STL都是使用std::allocator)
  • 方案二: 使用更加原始的::operatornew 和::operatordelete 来管理内存(operator new/operator delete 和new/delete 表达式不一样,new/delete 表达式既分配内存也初始化对象,operatornew 只分配内存,std::allocator和new/delete表达式都是调用operatornew/operator delete 实现)
  • 方案三: 使用C 语言的malloc/free来管理内存(C 的函数一定比C++的更高效吗?)

目前先实现使用std::allocator 的方案,后实际和理论分析结合判断哪种方案更优

2.    初始内存以及内存增长方式

  • 方案一:

默认内存分配方式为初始分配2*sizeof(T) 大小的内容,随着使用情况,以乘2方式增长内存

  • 方案二:

默认构造时不分配内存,在push 进第一个元素时分配内存(标准STL的实现),增加方式使用2的n 次方

疑问: 为什么std::vector使用 2的n次方这种空间增长方式?   这种方式是最优的吗?

目前先实现方案二,同时继续探索最优的增长方式

3.    构造函数

初期先实现空的构造函数,后续再实现多种指定初值的构造函数

4.    Capacity 和size

使用3个指针来标识元素在内存中的情况: first _element指向第一个元素(分配空间的起始位置),end指向分配空间的结束位置,first_free指向未构造元素的第一个位置。故vector的size(实际使用元素数量)= first_element – first_free

Vector 的capacity (在重新分配内存前,可以定义的元素总数) =  end – first_element,如下图所示:

以上就是 c++ stl vector 的主要设计, 接下来就是要实现的接口

 二. 目前实现的接口             

接口接口类别作用
vector<T>constructor默认构造函数,构造空的vector 对象
vector(size_type count,const T& t)constructor构造函数
vector(const vector&);constructor赋值构造函数,使用已存在vector<T> 类型对象构造一个新的副本
size()Capacity获得vector 当前元素个数
capacity()Capacity获取vector 已分配空间大小
void reserver(unsignedlong arg_capacity);Capacity设置vector 的capacity 大小
void shrink_to_fit()Capacity释放未使用的capacity 空间
bool empty()Capacity判断vector 是否为空
max_size()Capacity给出vector<T> 理论上最大元素个数
void push_back(constT& element_t);Modifiers添加一个元素到vector
void pop_back()ModifiersRemove the last element of vector
void clear()ModifiersRemove all element of vector. Implemented by calling pop_back()
void resize(size_type count)ModifiersResize the container to hold count elements ,use push_back & pop_back()  implement.
iterator erase(iterator& pos)ModifiersErase element in pos position
iterator& insert(iterator& pos,constT& value)ModifiersInsert element before pos
const  T& front()constElement access
Get first element
T& front()Element access
Non const front()
const  T& back()constElement access
Get last element
T& back()Element access
Non const back()
const T& at(size_typepos)constElement access
Get the pos element
T& at(size_typepos)Element access
Non const at
const T&operator[] (size_typepos)constElement access
Overloaded operator[]
const T&operator[] (size_typepos)Element access
Non const overloaded operator[]
T* data()Element access
Returns pointer to the underlying array serving as element storage    暂未实现,待考虑
iterator& begin()IteratorsReturn iterator to the first element
iterator& end()IteratorsReturn iterator to the  element following the last element

上表就是 c++ stl vector 已实现的接口列表.

 三. 实现过程中遇到的问题与解决

1.   使用模板时,出现链接错误

模板实例化时,编译器需要能看到完整的模板定义代码, 而不像普通函数使用时只需看到声明,模板的编译常见的有两种模型:包含编译模型,分离编译模型

目前主流的编译器如VS等不支持分离编译模型, 故使用包含编译模型

2.     编译时,出现非常多的如下错误:

error C2995: “tiny::vector<T>::~vector(void)”: 函数模板已经定义

还是模板编译的问题,目前来看,模板使用包含编译模型比较好的实践是,将模板声明和实现都放在头文件中,使用模板时include模板头文件即可

参考:http://blog.csdn.net/xiaoyaohuqijun/article/details/50558208

3.      在向vector 添加第三个元素时,进程卡住不往下走

原因:  在重新分配内存的函数中,delete 操作删除了后续还需要使用的内存或者已经删除了的内存

//doubles the allocated memory
       template<classT>voidvector<T>::reallocate()
       {
              unsigned long _size = first_free - first_element;
              unsigned long _capacity = end - first_element;
 
 
 
              //allocatenew memory block
              T* temp = av.allocate(2 * _capacity);
 
              T* p1 = temp;
              T* p2 = first_element;
 
              //copyelement to new memory block and delete element in old memory block
              for (unsigned i = 0; i < _size; ++i)
              {
                     av.construct(p1 + i, *(p2 +i));
                     av.destroy(p2 + i);
 
              }
 
              //freeold memory block
              av.deallocate(first_element,_capacity);
 
 
              //adjust cursor pinters;
              first_element = temp;
              first_free = first_element +_size;
              end = first_element + 2 *_capacity;
 
              deletep1;
              delete p2;
       }
 
 
}

  P1 指向新分配的内存,delete p1会导致后面的操作出现未定义行为

  P2 指向的内存在前面已经由deallocate 释放,再删除也是未定义的行为

4.      实现max_size() 时的遇到困难,完全没有思路

答: 由于是使用std::allocator来分配内存,某一具体的vector<T>的maxsize 受allocator的限制:

pointer allocate( size_type n, std::allocator<void>::const_pointer hint = 0 );

从allocator的allocate操作看,max size 受以下两个因素限制

allocator 调用operatornew 能够分配的最大连续内存

size_type 类型的最大值

size_type 定义在std::vector中,为unsignedint, C++ 标准库<climits>  (从C标准库limit.h 而来) 定义了unsigned int 的最大值宏UINT_MAX,  可以将该值打印出来(32位编译器):

4294967295

std::vector 的max_size() 不是这个值,说明还受其他限制

std::vector<int>   和std::vector<std::string>的max_size() 是一样的。。。。。

std::vector<T>  自定义类型T 的max_size() 不一样,说明max_size() 与元素类型T还是有关的

答:  目前STLmax_size()都给出的是理论的上限值。

     对于32位系统(程序)来说,最大能利用的内存为2^32-1. 故vecot<int>的max_size() 为  2^32/sizeof(int) -1 ,为1073741823;   vector<char>的max_size()为2^32-1.

     总结来说,vector<T>的max_size()的计算方式为 max_size =  f/sizeof(T) -1,其中f在32系统中为2^32, 在64位系统中为2^64。

可以通过指针类型的size来判断系统(应用)的位数,32时指针的sizeof为4,,64位时指针的sizeof 为8。

		//get max_size() of vector<T>
		size_type  max_size()  const
		{
			
			if (4 == sizeof(p_first_element))
			{
				return std::exp2(32)/ sizeof(T) -1;

			}
			else
			{
				return std::exp2(64) / sizeof(T) - 1;
			}

		}

5.      allocator 成员对象是否应该是static?

在实现copy constructor 时发现vector 的allcator 对象很难处理。 从std::allocator 使用上来说,在全局作用域内都可以只使用唯一一个std::allocator 对象。  所以将vector 的allocator 成员对象设置为static 是合理的,  vector 的某个实例的各个对象共用一个allcator 对象是合理的。

6.      构造函数初始化列表需要在函数声明和定义处都存在吗?

不需要,构造函数初始化列表只需要在函数定义处存在即可,准确来说,构造函数初始化列表只能在构造函数实现处出现

7.      调用push_back() 等操作Visual sudio 报如下错误:

error LNK2001: 无法解析的外部符号 “private: static class std::allocator<int>tiny::vector<int>::av” (?av@?$vector@H@tiny@@0V?$allocator@H@std@@A)

1>  D:\projects\visualStudio\TinySTL\Debug\TinySTL.exe: fatal error LNK1120: 1 个无法解析的外部命令

各种编译器关于模板的错误提示真是shit 一样烂,template使用就这点最烦。这个问题没有想到特别好的办法,因为只调用了默认构造函数,可以看出应该是实例化类模板时某个地方出现了异常。VS的提示也看不出什么,还好目前实现的接口数量还比较少,所以决定一个一个接口排查

接口是否导致该异常
Vector<T>N
size()N
capacity()N
bool empty()N
void push_back(constT& element_t);Y
void pop_back()NA
void reserver(unsignedlong arg_capacity);NA
void shrink_to_fit()NA

通过非常土的一个一个排查,发现成员函数中调用负责内存管理的std::allocator 类型对象av会导致该异常,av 的的定义如下:

           <span style="color:#ff0000;">static std::allocator<T> av;  //default allocator of vector</span>
              T* first_element;   //pointer to first element (pointer to start of allocated memory)
              T* first_free;       //pointer  after the last element (pointerto the start of allocated but not constructed element)
           T* end;              //pointer to end of allocated memory

将av 定义为static类型会导致该异常,原因是static 类型对象的特殊性导致的。

staticstd::allocator<T> av 只是声明了变量,而没有定义变量,static 成员变量需要通过赋值初始化来定义,初始化static成员的几种方式见:

http://blog.csdn.net/poechant/article/details/6334121

C++静态成员详解参考:

http://blog.chinaunix.net/uid-21411227-id-1826743.html

类模板在运行时才实例化。  通过 tiny::vector<T> iv;  iv.push_back(1)   实 例    化vector<int>::push_back() 时,实例化的vector<int>中av还是未定义。  所以push_back() 调用av 就会异常,因为访问了未定义的对象

C++语言规定:static 成员需要在类外初始化,所以此处在类外加上如下初始化语句即解决了该异常:

template<class T> std::allocator<T> vector<T>::av = std::allocator<T>();

8.      调用Copy Constructor 时,出现语法错误:

error C2662: “unsignedlong tiny::vector<int>::size(void)”: 不能将“this”指针从“consttiny::vector<int>”转换为“tiny::vector<int>&”:

CopyConstructor 定义如下:

//copyconstructor
       template<class T> vector<T>::vector(const vector&vt):first_element(0),first_free(0),end(0)
       {
              unsignedlong _size = vt.size();
              unsigned long _capacity =vt.capacity();
 
              T* ptemp = av.allocate(_capacity );
              first_element = ptemp;
      
 
              for (unsigned int i = 0; i < _size ; ++i)
              {
 
                     av.construct(first_element+ i, *(vt.first_element+ i));
              }
 
 
              first_free = first_element +_size;
              end = first_element + _capacity;
 
}

  主要是如下两句报错:

              unsignedlong _size = vt.size();
              unsigned long _capacity =vt.capacity();

 解决办法:  将size() 和capacity() 都声明为const 成员函数即可

 原因分析: const 对象不能修改数据内容, 所以const 类对象只能调用const成员函数,不能调用其他非const成员函数(因为const 类对象的this指针指向const对象的)

Eg.

class A
{
    public:
        A(float arg1 =0.0,float arg2 = 0.0):x(arg1),y(arg2){}
 
        void test1(){std::cout<<" test1 being called"<<std::endl;}
 
        voidtest2()  const {std::cout<<"test2 being called"<<std::endl;}
 
    private:
        float x;
        float y;
};
 
 
//test code
int main()
{
   const A a;
   a.test1();
   a.test2();
 
    return 0;
}

a对象调用test1()是非法的,const对象只能调用const成员函数。所以对不修改对象内容的成员函数,最好还是都声明为const为好。EfectiveC++中也说到,将const实施于成员函数的目的,是为了确认该函数可作用于cosnt对象上。

9.      CopyConstructor 是否需要将预留的未构造元素的空间也copy 出来?

通过copy constructor 构造出来的对象,是否需要保留和源对象一样的capacity? 因为用户的行为难以预测,所以最好就是构造时,不要预留多的空间,和默认构造函数的初始分配保持一致。这样构造函数原则上就一致了,构造函数不负责预留空间,在push_back() 时动态调整内存。所以copy constructor 构造新对象时,不copy 未使用的空间。

10.  实现的max_size() 的返回值一直为2^32-1 , 不随vector<T>  T的类型变化而变化

       unsigned longmax_size()  const
      {
              unsigned longmax_limit = 0;
             if (4== sizeof(first_element))
             {
                     max_limit= std::exp2(32);

              }
             else
            {
                     max_limit= std::exp2(64);
             }

            return (max_limit / sizeof(T)) -1;
       }

原因分析:   long 类型在32机器上一般以32位长度表示,最大值为2^32-1, 把std::exp2(32)(2^32) 赋给max_limit 刚好超过了long类型的表示范围。

解决办法:

  考虑到中间环节可能需要计算2^64 (long long 类型也刚好表示不了), 所以不存储中间计算结果。

而max_size()  在64 位机器上最大刚好为2^64-1,  可以用long long 类型表示,故将所有相关类型接口放回值从long 类型改为long long 类型,接口修改为如下形式:

    unsigned long long max_size()  const
     {
              
          if (4 == sizeof(first_element))
             {
                   return std::exp2(32)/ sizeof(T)-1;

            }
            else
         {
                   returnstd::exp2(64) / sizeof(T) -1;
           }

             
      }

为了防止以后再次需要更改类型,需要修改多处类型,在模板类内部定义自己的类型:

typedef unsignedlonglong   size_type;

最后max_size() 的实现为:

              size_type max_size()  const
              {
                    
                     if (4== sizeof(first_element))
                     {
                           returnstd::exp2(32)/ sizeof(T)-1;
 
                     }
                     else
                     {
                           returnstd::exp2(64) / sizeof(T) -1;
                     }
 
           }

11.   iterator 设计

Iterator 平时使用很熟悉,但是当为自己设计的类添加迭代器时,发现自己对迭代器是什么没有很清晰的理解,所以要好好梳理学习下iterator的相关内容。

目前iterator相关的概念concept 有如下几个:

Iterator

The Iterator conceptdescribes types that can be used to identify and traverse theelements of a container.

Iterator is the baseconcept used by other iterator types: InputIteratorOutputIteratorForwardIterator,BidirectionalIterator, and RandomAccessIterator. Iteratorscan be thought of as an abstraction of pointers.

Requirements

The type It satisfies Iterator if

§  The type It satisfies CopyConstructible, and

§  The type It satisfies CopyAssignable, and

§  The type It satisfies Destructible, and

§  lvalues of type It satisfy Swappable, and

Given

§  r, an lvalue of type It,

§  reference, the type denoted by std::iterator_traits<It>::reference

The following expressions must bevalid and have their specified effects:

ExpressionReturn TypePrecondition
*runspecifiedr is dereferenceable (see below)
++rIt&r is incrementable (see below)

Dereferenceableiterators

Iteratorsfor which the behavior of the expression *i is defined are called dereferenceable.

Iterators are not dereferenceable if

§  they are past-the-end iterators (including pointers past the end of an array) or before-begin iterators. Suchiterators may be dereferenceable in a particular implementation, but thelibrary never assumes that they are.

§  they are singular iterators, that is, iterators that arenot associated with any sequence. A null pointer, as well as adefault-constructed pointer (holding an indeterminate value) is singular

§  they were invalidated by one ofthe iterator-invalidating operations on the sequence to which they refer.

Iteratorsfor which the behavior of the expression ++i is defined are called incrementable.

简单翻译下

Iterator 是这样一种用来标识和遍历容器元素的类型

类型It 是Iterator 如果满足

1.    It 是CopyConstrucible

2.    It 是CopyAssignable

3.    It 是Destuctible

4.    It 类型的左值是Swappable

且如果r 是It 类型的左值, 一下表达式必须是可用的

5. *r ,  表示 r is dereferenceable

6.  ++r , 表示 r is  incrementable

总结来说, Iterator 类型需要满足6点要求:

1.  CopyConstructible

Specifiesthat an instance of the type can be copy-constructed from an lvalueexpression.

The type T satisfies CopyConstructible if

§  The type T satisfies MoveConstructible, and

Given

§  v, an lvalue expression of type T or const T or an rvalue expression of type const T

§  u, an arbitrary identifier

The following expressions must bevalid and have their specified effects

ExpressionPost-conditions
T u = v;The value of u is equivalent to the value of v.The value of v is unchanged
T(v)The value of T(v) is equivalent to the value of v.The value of v is unchanged.
The expression v.~T() also must be valid, and, for lvalue v, the expression &v must have the type T*or const T* and must evaluate to the address of v

Specifies that an instance of thetype can be constructed from an rvalue argument.

Requirements

The type T satisfies MoveConstructible if

Given

§  rv, an rvalue expression of type T

§  u, an arbitrary identifier

The following expressions must bevalid and have their specified effects

ExpressionPost-conditions
T u = rv;The value of u is equivalent to the value of rv before the initialization.The new value of rv is unspecified
T(rv)The value of T(rv) is equivalent to the value of rv before the initialization.The new value of rv is unspecified.

2.  CopyAssignable

Specifies that an instance ofthe type can be copy-assigned from an lvalueexpression.

Requirements

The type T satisfies CopyAssignable if

§  The type T satisfies MoveAssignable, and

Given

§  t, a modifiable lvalue expression of type T

§  v, an lvalue expression of type T or const T or an rvalue expression of type const T

The following expressions must bevalid and have their specified effects

ExpressionReturn typeReturn valuePost-conditions
t = vT&tThe value of t is equivalent to the value of v.The value of v is unchanged.

Specifiesthat an instance of the type can be assigned from an rvalue argument.

Requirements

The type T satisfies MoveAssignable if

Given

§  t, a modifiable lvalue expression of type T

§  rv, an rvalue expression of type T

The following expressions must bevalid and have their specified effects

ExpressionReturn typeReturn valuePost-conditions
t = rvT&tThe value of t is equivalent to the value of rv before the assignment.The new value of rv is unspecified

3.  Destructible

Specifies that an instance of thetype can be destructed.

Requirements

The type T satisfies Destructible if

Given

§  u, a expression of type T

The following expressions must bevalid and have their specified effects

ExpressionPost-conditions
u.~T()All resources owned by u are reclaimed, no exceptions are thrown.

4.   Lvalue  Swappable

Any lvalue or rvalue of thistype can be swapped with any lvalue or rvalue of some other type, usingunqualified function call swap() inthe context where both std::swap and the user-defined swap()s are visible.

5.      Dereferenceable

6.      Incrementable

从迭代器类型的定义和要求来看,指针是迭代器的一种(实现与概念原型),迭代器是C 语言中指针概念的扩展。指针是迭代器,迭代器却不一定是指针。

验证C++中指针满足迭代的6点要求:

intmain()
{
    typedef int *  INTP;
    INTP p1 = 0;
 
    int i = 5;
    int j = 6;
 
    //moveConstructible
    INTP p2(p1);
    INTP p3 = p1;
 
    //copyConstructible
    INTP p4(&i);
    INTP p5 = &i;
 
 
    //copyAssignable
    p3 = &i;
 
 
    //destructible is obvious true
 
 
    //swappable
    INTP pp1 = &i;
    INTP pp2 = &j;
    std::swap(pp1,pp2);
 
    //dereferenceable
   std::cout<<*pp1<<"\t"<<*pp2<<std::endl;
 
    //incrementable
    ++pp1;
 }

12  使用自定义的迭代器遍历vector 时,提示

: error LNK2019: 无法解析的外部符号 “bool __cdecl tiny::operator!=(classtiny::Iterator<int> &,class tiny::Iterator<int> &)”(??9tiny@@YA_NAAV?$Iterator@H@0@0@Z),该符号在函数 _main 中被引用

调用iterator重载的operator==和operator!=函数时都会提示该错误。

原因:  模板类的友元声明错误

友元声明为:

template<class T>
       class Iterator
       {
              friend  bool operator!= (Iterator& iv1, Iterator& iv2);
 
 
              //………..
        }

真正重载的函数为:

       template<class T> Iterator<T>& operator+(Iterator<T>& iv, size_type pos)
       {
 
              iv.pIterator += pos *sizeof(T);
 
              return iv;
       }
 

友元声明中是将一个非模板函数 bool operator!=  声明为友元

而重载实现的是一个模板函数template<classT>Iterator<T>&operator+(Iterator<T>&iv,size_typepos), 两者根本不是同一个函数,不是同一个东西。

解决办法:  友元声明与重载实现的保持一致即可,有多重方式,例如:

template<class T>
       class Iterator
       {
              template<typename x> friend  Iterator&operator+(Iterator<x>& iv, size_type<x> pos);
              template<typename x> friend  booloperator==(Iterator<x>& iv1, Iterator<x>& iv2);
              template<typename x> friend  booloperator!= (Iterator<x>& iv1, Iterator<x>& iv2);
 
 
              template<class X> friend class vector;
 
             ………………….
            }
 

FYI:

重新学习了下C++Primer , 对于模板类友元声明归纳总结如下:

c++ stl vector vector 设计 vector 实现
模版友元声明

13. 实现erase() 函数时报了很多异常:

1>d:\projects\visualstudio\tinystl\tinystl\vector.h(420):warning C4346: “iterator”: 依赖名称不是类型

1> d:\projects\visualstudio\tinystl\tinystl\vector.h(420): note: 用“typename”为前缀来表示类型

1>d:\projects\visualstudio\tinystl\tinystl\vector.h(420):error C2061: 语法错误: 标识符“iterator”

1>d:\projects\visualstudio\tinystl\tinystl\vector.h(430):error C2065: “T”: 未声明的标识符

1>d:\projects\visualstudio\tinystl\tinystl\vector.h(430):error C2923: “tiny::vector”: 对于参数“T”,“T”不是有效的 模板 类型变量

1>d:\projects\visualstudio\tinystl\tinystl\vector.h(431):error C2143: 语法错误: 缺少“;”(在“{”的前面)

1>  d:\projects\visualstudio\tinystl\tinystl\vector.h(431):error C2447: “{”: 缺少函数标题(是否是老式的形式表?)

编译器关于模板的异常信息一如既往的没什么用~~~~~~~~~~~~

erase()的声明与实现如下:

template<class T>
       class vector
       {
                    
 
       public:
              typedef   Iterator<T>    iterator;
 
          …………..
      iteratorerase(iteratorpos);
}
 
 
 
 
 
       template<class T> vector<T>::iterator  vector<T>::erase(iterator   pos)
       {
              return pos;
       }
 

解决办法:

将函数实现中的返回类型改为Iterator<T>  即可(非最优,破坏了typedef 的意图,在弄清楚原因已修改为使用typename 关键词:

template<class T> typename vector<T>::iterator& vector<T>::erase(iterator&   pos)
       {
 
              T* ptemp = pos.getPointer()+1;
 
              while (ptemp != p_first_free)
              {
                     *(ptemp-1) = *ptemp ;
 
                     ptemp += 1;
 
              }
 
              pop_back();
 
       return pos;
       }

原因:

 编译器不知道  vector<T>::iterator    是一个类型名,还是一个对象名,因为iteraotr还可能是类的static 成员

前面的解决办法不是特别好,破坏了typedef 类型的目的,最好的办法是使用typename,参考typename 在内嵌依赖类型名上的作用:

http://blog.csdn.net/xiaoyaohuqijun/article/details/51018796

14. insert() 中出现死循环

template<class T> typename vector<T>::iterator& vector<T>::insert(iterator&pos, const T&value)
       {
 
             
 
              //callpush_back to add element , and double size vector if necessary
              push_back(value);
 
              T* ptemp = pos.getPointer();
              T* index = p_first_free - 1;
 
              while (index != ptemp)
              {
                     *index = *(index -1);
                     index -= 1;
              }
 
              av.construct(ptemp, value);
 
              return pos;
       }

原因:push_back 过程中内存有重新分配,pos 的值发生改变,失效(也就是常常说的,Container 元素个数发生变化时,iterator失效问题)

解决办法: 将iterator转化为相对于第一个元素的偏移量,重新分配内存后根据偏移量算出新的对应的指针

 
       template<class T> typename vector<T>::iterator& vector<T>::insert(iterator&pos, const T&value)
       {
 
              T* ptemp = pos.getPointer();
              size_typedif = ptemp - p_first_element;
 
              //callpush_back to add element , and double size vector if necessary
              //push_back(value);
              push_back(value);
 
              T*ptemp_new = p_first_element + dif;
              T* index = p_first_free - 1;
      
 
              while (index != ptemp_new)
              {
                     *index = *(index -1);
                     index -= 1;
              }
 
              av.construct(ptemp_new, value);
 
              return pos;
       }

以上就是关于 c++ stl vector 设计与实现 的内容。

1 thought on “C++ TinySTL 之vector 设计,实现与中间的坑”

  1. Pingback: c++ stl 实现 stl 设计 -- TinySTL设计与实现 – The Hu Post

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top