想实现自己的一个STL。先写vector。
vector实际上是一个泛型变长数组。基本的数组操作都是很直观的,难点在于变长和对泛型的支持。
变长的支持:
变长,简单来说,就是在向一个vector对象中增加元素时,vector会随着元素的增加而动态扩容。
因此,用C语言中的malloc和free便可实现vector的变长与析构。但这只能实现对内置类型的支持。若要对泛型元素进行支持,C语义的malloc与free无法进行显示的构造函数和析构函数的调用。这时便要用到C++的内存分配机制。
C++的内存分配的风格一般是new和delete,然而我们知道,如果要在每次向vector对象增加元素时,便重新申请一个新的长度的数组,再将原来的元素copy过去,这样的开销是不可承受的。所以vector的内存分配策略往往是这样:当元素个数为N时,数组的长度为L=2^K,其中K为非负整数,使得2^(K-1) < N <= 2^K。这时就需要一个机制,可以1.分配或释放未构造(即未调用构造函数,或已调用了析构函数)的内存;2.在给定的内存区域进行构造(调用构造函数)与析构(调用析构函数)。
C++提供了两种方法,这两种方法均可独立地支持上述机制的两个功能,且可以混合操作。即上述可用第一种方法实现功能1,而用第二种方法实现功能2。反之亦然。
1.使用allocator类,allocator类提供了allocate(n)与deallocate(p,n)这两个函数来分配和回收未构造的内存,提供了construct(p, t)与destroy(p)这两个函数来对一块未构造的内存进行构造,或对已构造的内存进行析构。
这种方式的优点在于类型安全,因为allocator类是类模板,在使用时需要指定模板参数(即分配或销毁的类),这样在分配内存时,只需指出分配的对象的数目即可。
缺点在于构造内存时只能用复制构造函数,不够灵活。(只能用复制构造函数,连默认的构造函数也无法使用哦~)
2. 使用operator new与operator delete进行分配和释放未构造的内存。用定位new表达式对未构造内存进行初始化,用显示调用对象的析构函数来撤销对象。
这种方法的优点在于使用定位new表达式时可以自由使用任意构造函数。缺点在于operator new在申请内存时需要明确内存的具体大小(即字节数),容易出错。
无论采用何种方法,均有有uninitialized_xxxx()函数族来使用迭代器对连续的内存进行批量构造(也是使用复制构造函数)。
下面阐述后来被证伪:
由于使用allocator类时,对每一个vector类模板的实现,都有一个静态的allocator对象,导致了一种很不好的结果。阐述如下:
因为vector是类模板,所以其声明与实现需要在同一个文件中(编译需要,我也不是很清楚为什么),这个文件的后缀我将它命名为.hpp。但一旦该类模板中出现的静态成员,由于前述约束,该静态成员也必须在这个文件中进行初始化。这就导致了,如果有多个.cpp的源文件要使用vector,包含了vector.hpp时,上述静态成员便在多处进行了初始化,导致编译错误。
在网上也查到了几个解决方法,但有的无法实现,有的约束过多。
故这里统一采用operator new与operator delete的方法来申请和释放未构造的内存。
后来发现,多文件包含后并没有产生静态变量多处定义的错误。错误原因为:当多文件包含vector.hpp时,由于main.cpp在包含vector.hpp之前包含了iostream,而另一个cpp文件只包含了vector.hpp,而在vector.hpp中使用的std::length_error。这就导致了另一个cpp文件包含的vector.hpp文件中没有std::length_error的定义,导致语法分析出错。
但仍然使用operator new与operator delete,并结合uninitialized_xxxx函数族进行内存管理。
对泛型的支持:
最直观地,加上模板参数即可。但问题在于对迭代器的支持。
待续