4.STL简单容器(cghVector)的实现

我用VS2013写的程序(github ),vector版本的代码位于cghSTL/version/cghSTL-0.3.1.rar

我是照着侯捷老师的《STL源码剖析》做的cghSTL,现在才看到第三章,觉得这本书的编排非常适合自学。

第一章讲解空间配置器,这是STL最基础的部件,没什么好说的。

第二章讲解迭代器,老师举了单向链表的例子,这非常有考究,单向链表不需要连续的存储空间,意味着使用单向链表实现的容器不需要考虑空间溢出等问题,我们可以把注意力集中在容器的迭代器上。

第三章讲解序列式容器,第一节介绍vector的实现,vector需要考虑空间的连续使用和空间溢出等问题,而正因为vector维护的是连续线性空间,所以不论其元素类型为何,普通指针都可以作为vector的迭代器而满足所有必要条件!这成功的避开了设计容器时不得不分散精力设计迭代器的坑!

侯捷老师写书真是有水平啊!

言归正传,接下来我们看看一个vector的实现细节。

我们一共有四个文件:

1.      globalConstruct.h,构造和析构函数文件,位于allocator/cghAllocator

2.      cghAlloc.h,空间配置器文件,位于allocator/cghAllocator文件夹下

3.      cghVector.h,容器的实现文件,位于cghSTL/sequence
containers
/cghVector/

4.      cghSTL.cpp,测试文件,位于cghSTL/test/

先看第一个,globalConstruct.h构造函数文件

/*******************************************************************
*  Copyright(c) 2016 Chen Gonghao
*  All rights reserved.
*
*  [email protected]
*
*  功能:全局构造和析构的实现代码
******************************************************************/

#include "stdafx.h"
#include <new.h>
#include <type_traits>

#ifndef _CGH_GLOBAL_CONSTRUCT_
#define _CGH_GLOBAL_CONSTRUCT_

namespace CGH
{
	#pragma region 统一的构造析构函数
	template<class T1, class  T2>
	inline void construct(T1* p, const T2& value)
	{
		new (p)T1(value);
	}

	template<class T>
	inline void destroy(T* pointer)
	{
		pointer->~T();
	}

	template<class ForwardIterator>
	inline void destroy(ForwardIterator first, ForwardIterator last)
	{
		// 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial
		// non-trivial的元素可以直接释放内存
		// trivial的元素要做调用析构函数,然后释放内存
		for (; first < last; ++first)
			destroy(&*first);
	}
	#pragma endregion
}

#endif

按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型,non-trivial的元素可以直接释放内存,可以理解为non-trivial全部分配在栈上,不用我们操心,由程序自动回收,trivial的元素要分配在堆上,需要手工调用析构函数来销毁,然后释放内存。

cghAlloc.h是空间配置器文件,空间配置器负责内存的申请和回收。

/*******************************************************************
*  Copyright(c) 2016 Chen Gonghao
*  All rights reserved.
*
*  [email protected]
*
*  功能:cghAllocator空间配置器的实现代码
******************************************************************/

#ifndef _CGH_ALLOC_
#define _CGH_ALLOC_

#include <new>
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>

namespace CGH
{
	#pragma region 内存分配和释放函数、元素的构造和析构函数
	// 内存分配
	template<class T>
	inline T* _allocate(ptrdiff_t size, T*)
	{
		set_new_handler(0);
		T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
		if (tmp == 0)
		{
			std::cerr << "out of memory" << std::endl;
			exit(1);
		}
		return tmp;
	}

	// 内存释放
	template<class T>
	inline void _deallocate(T* buffer)
	{
		::operator delete(buffer);
	}

	// 元素构造
	template<class T1, class  T2>
	inline void _construct(T1* p, const T2& value)
	{
		new(p)T1(value);
	}

	// 元素析构
	template<class T>
	inline void _destroy(T* ptr)
	{
		ptr->~T();
	}
	#pragma endregion

	#pragma region cghAllocator空间配置器的实现
	template<class T>
	class cghAllocator
	{
	public:
		typedef T			value_type;
		typedef T*			pointer;
		typedef const T*	const_pointer;
		typedef T&			reference;
		typedef const T&	const_reference;
		typedef size_t		size_type;
		typedef ptrdiff_t	difference_type;

		template<class U>
		struct rebind
		{
			typedef cghAllocator<U> other;
		};

		static pointer allocate(size_type n, const void* hint = 0)
		{
			return _allocate((difference_type)n, (pointer)0);
		}

		static void deallocate(pointer p, size_type n)
		{
			_deallocate(p);
		}

		void construct(pointer p, const T& value)
		{
			_construct(p, value);
		}

		void destroy(pointer p)
		{
			_destroy(p);
		}

		pointer address(reference x)
		{
			return (pointer)&x;
		}

		const_pointer const_address(const_reference x)
		{
			return (const_pointer)&x;
		}

		size_type max_size() const
		{
			return size_type(UINT_MAX / sizeof(T));
		}
	};
	#pragma endregion

	#pragma region 封装STL标准的空间配置器接口
	template<class T, class Alloc = cghAllocator<T>>
	class simple_alloc
	{
	public:
		static T* allocate(size_t n)
		{
			return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T));
		}

		static T* allocate(void)
		{
			return (T*)Alloc::allocate(sizeof(T));
		}

		static void deallocate(T* p, size_t n)
		{
			if (0 != n)Alloc::deallocate(p, n*sizeof(T));
		}

		static void deallocate(T* p)
		{
			Alloc::deallocate(p, sizeof(T));
		}
	};
	#pragma endregion
}

#endif

class cghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。

我们自己写的空间配置器必须封装一层STL的标准接口,

template<classT, class Alloc = cghAllocator<T>>
class simple_alloc

vector的实现代码如下:

cghVector.h

/*******************************************************************
*  Copyright(c) 2016 Chen Gonghao
*  All rights reserved.
*
*  [email protected]
*
*  功能:cghVector容器的实现代码
******************************************************************/

#ifndef _CGH_VECTOR_
#define _CGH_VECTOR_

#include "globalConstruct.h"
#include "cghAlloc.h"
#include <memory>

namespace CGH
{
	template<class T, class Alloc = cghAllocator<T>>
	class cghVector
	{
	public:
		typedef T				value_type;
		typedef value_type*		pointer;
		typedef value_type*		iterator;
		typedef value_type&		reference;
		typedef size_t			size_type;
		typedef ptrdiff_t		difference_type;

		//typedef __false_type	has_trivial_default_constructor;
		//typedef __false_type	has_trivial_copy_destructor;
		//typedef __false_type	has_trivial_assignment_operator;
		//typedef __false_type	has_trivial_destructor;
		//typedef __false_type	is_POD_type;

	protected:
		typedef simple_alloc<value_type, Alloc> data_allocator; // 定义空间配置器
		iterator start;
		iterator finish;
		iterator end_of_storage;

		void insert_aux(iterator position, const T& value)
		{
			if (finish != end_of_storage)
			{
				construct(finish, *(finish - 1));
				++finish;
				T x_copy = value;
				std::copy_backward(position, finish - 2, finish - 1);
				*position = x_copy;
			}
			else
			{
				ptrdiff_t old_size = size();
				const size_type len = old_size != 0 ? 2 * old_size : 1;
				/*
					配置原则:如果原大小为0,则配置1个元素大小
					如果原大小不为0,则配置原大小的两倍
				*/
				iterator new_start = data_allocator::allocate(len);
				iterator new_finish = new_start;
				try
				{
					// 把 start 到 position 这段内存拷贝到 new_start 处,返回 new_finish = new_start + ( position - start )
					new_finish = std::uninitialized_copy(start, position, new_start);
					construct(new_finish, value); // 在 new_finish 处构造新元素
					++new_finish;
					//new_finish = std::uninitialized_copy(position, finish, new_finish);
				}
				catch (std::exception ex)
				{
					// 如果执行失败就要回滚
					destroy(new_start, new_finish);
					data_allocator::deallocate(new_start, len);
					throw;
				}
				destroy(begin(), end());
				deallocate();
				start = new_start;
				finish = new_finish;
				end_of_storage = new_start + len;
			}
		}

		#pragma region 释放内存和析构元素
		void deallocate()
		{
			if (start)
			{
				data_allocator::deallocate(start, end_of_storage - start);
			}
		}
		#pragma endregion

		#pragma region 分配内存和构造元素
		/*
		 *	fill_initialize和allocate_and_fill把cghVector的初始化分为了两步:
		 *	1.fill_initialize的职责是分配一段内存
		 *	2.fill_initialize调用allocate_and_fill,在分配的内存中调用构造函数创建cghVector的元素
		 */
		void fill_initialize(size_type n, const T& value)
		{
			start = allocate_and_fill(n, value);
			finish = start + n;
			end_of_storage = finish;
		}

		iterator allocate_and_fill(size_type n, const T& x)
		{
			iterator result = data_allocator::allocate(n);
			iterator cur = result;
			for (; n > 0; --n, ++cur)
			{
				construct(&*cur, x);
			}
			return result;
		}
		#pragma endregion
	public:
		#pragma region 对cghVector的读操作
		iterator begin(){ return start; } // 返回cghVector头元素的地址
		iterator end(){ return finish; } // 返回cghVector尾元素的地址
		size_type size(){ return size_type(int(end() - begin())); } // cghVector的长度 = 尾元素地址 - 头元素地址
		size_type capacity()const{ return size_type(end_of_storage - start); } // cghVector的容量 = 最大容量地址 - 头元素地址
		bool empty()const { return begin() == end(); } // cghVector是否为空:头元素地址 是否等于 尾元素地址
		reference operator[](size_type n){ return *(begin() + n); } // 返回指定位置的元素引用
		reference front(){ return *begin(); } // 获得头元素位职
		reference back(){ return *(end() - 1); } // 获得尾元素位置
		#pragma endregion

		#pragma region 构造函数
		cghVector() :start(0), finish(0), end_of_storage(0) { } // 初始化空的cghVector
		cghVector(size_type n, const T& value){ fill_initialize(n, value); } // 初始化包含n个值为value的cghVector
		cghVector(int n, const T& value){ fill_initialize(n, value); } // 同上
		cghVector(long n, const T& value){ fill_initialize(n, value); } // 同上
		explicit cghVector(size_type n){ fill_initialize(n, T()); } // 初始化cghVector的长度为n
		#pragma endregion 

		#pragma region 析构函数
		~cghVector()
		{
			destroy(start, finish); // 先调用cghVector中元素的析构函数
			deallocate(); // 再释放cghVector占用的内存
		}
		#pragma endregion

		#pragma region 对cghVector的写操作
		/**
		*  弹出尾元素
		*/
		void pop_back()
		{
			--finish;
			destroy(finish);
		}

		/**
		*  在cghVector末尾插入一个元素
		*/
		void push_back(const T& x)
		{
			// 判断cghVector的容量是否满了,如果没满我们直接在已有的内存区域上构造元素
			if (finish != end_of_storage)
			{
				construct(finish, x);
				++finish;
			}
			else // 如果满了我们就要重新分配内存并重新构造函数
			{
				insert_aux(end(), x);
			}
		}

		/**
		*  清除[first, last)区间内的元素
		*/
		iterator erase(iterator first, iterator last)
		{
			// 把last到finish这段元素拷贝以first作为起始处的内存空间,返回first + ( finish - last ) 的地址
			iterator i = std::copy(last, finish, first);
			destroy(i, finish); // 销毁(first + ( finish - last ), finish]这段内存
			finish = finish - (last - first); // 重新设置finish
			return first;
		}

		/**
		*  清除某个位置上的元素
		*/
		iterator erase(iterator position)
		{
			if (position + 1 != end())
			{
				copy(position + 1, finish, position);
			}
			--finish;
			destroy(finish);
			return position;
		}
		#pragma endregion
	};
}

#endif

cghVector实现了pop_back、push_back、erase。

注释已经写得足够详细。 cghVector.h中调用了三个标准全局函数:std::copy_backward、std::uninitialized_copy、std::copy,这三个函数。

测试代码:

/*******************************************************************
*  Copyright(c) 2016 Chen Gonghao
*  All rights reserved.
*
*  [email protected]
*
*  文件名称:cghVector容器的测试代码
******************************************************************/

#include "stdafx.h"
#include "cghAlloc.h"
#include "globalConstruct.h"
#include "cghVector.h"
using namespace::std;

int _tmain(int argc,_TCHAR* argv[])
{
         using namespace::CGH;

         cghVector<int> test(2, 2);
         cout << "size = " <<test.size() << "\t";
         cout << "capacity = " <<test.capacity() << endl;

         test.push_back(1);
         cout << "size = " <<test.size() << "\t";
         cout << "capacity = " <<test.capacity() << endl;

         test.push_back(2);
         cout << "size = " <<test.size() << "\t";
         cout << "capacity = " <<test.capacity() << endl;

         test.push_back(3);
         cout << "size = " <<test.size() << "\t";
         cout << "capacity = " <<test.capacity() << endl;

         test.pop_back();
         cout << "size = " <<test.size() << "\t";
         cout << "capacity = " <<test.capacity() << endl;

         std::cout << test[2] << endl;// 返回指定位置处的元素,这里体现了vector迭代器的随机访问性质(random access iterators)

         test.erase(test.begin() + 1, test.begin()+ 2);
         test.erase(test.begin());
         for (cghVector<int>::iterator it =test.begin(); it != test.end(); it++)
         {
                   std::cout << *it <<std::endl;
         }
         system("pause");
         return 0;
}

可以看到当cghVector的大小(size)超过容量(capacity)时,cghVector自动扩容,每次扩充为原来的2倍。

时间: 2024-10-17 15:32:08

4.STL简单容器(cghVector)的实现的相关文章

STL简单 copy 算法的实现

1.简介 不论是对客户端或对STL内部而言,copy() 都是一个常常被调用的函数.由于copy进行的是复制操作,而复制操作不外乎运用赋值运算符(assignment operator)或复制构造函数(copy constructor),但是某些元素的类型是trivial assignment operator,因此如果能使用内存直接进行复制(例如使用C标准函数memmove.memcpy),便能节约大量时间.为此,copy算法用尽各种办法,包括函数重载(function overloading

STL简单hashtable的实现

1. 简介 Hash_table可提供对任何有名项(nameditem)的存取.删除和查询操作.由于操作对象是有名项,所以hash_table可被视为是一种字典结构(dictionary). Hash_table使用名为hashfaction的散列函数来定义有名项与存储地址之间的映射关系.使用hash faction会带来一个问题:不同的有名项可能被映射到相同的地址,这便是所谓的碰撞(collision)问题,解决碰撞问题的方法主要有三种:线性探测(linear probing).二次探测(qu

C++ STL vector容器学习

STL(Standard Template Library)标准模板库是C++最重要的组成部分,它提供了一组表示容器.迭代器.函数对象和算法的模板.其中容器是存储类型相同的数据的结构(如vector,list, deque, set, map等),算法完成特定任务,迭代器用来遍历容器对象,扮演容器和算法之间的胶合剂. 模板类vector 在计算中,矢量(vector)对应数组,它的数据安排以及操作方式,与array非常类似.在C++中,使用vector模板类时,需要头文件包含#include<v

STL简单&lt;stl_algorithms.h&gt;算法的实现

1.简介 STL标准中,没有区分基本算法和复杂算法,然而SGI STL却把常用的算法定义在<stl_algorithms.h>中.本文介绍部分<stl_algorithms.h>算法的实现,给出实现代码和测试代码. 本文介绍的算法包括: 1.      mismatch:比较两个序列,指出两者之间第一个不匹配的点,返回一对迭代器,分别指向两序列中不匹配的点: 2.      equal:如果两个序列在 [first, last ] 区间内相等,equal() 返回true,忽略第二

浅析常用STL中容器插入数据失败

昨天在上班的时候,碰到一个问题,关于 STL常用容器插入数据失败. 问题详细:在一个类构造函数填零后,那么map list 插入数据失败了,但是vector不会.测试代码如下: class Test { public: Test(){memset(this,0,sizeof(*this));} void InsertElement(){m_map.insert(std::make_pair(1,1)); } void InsetElement_vector(){ m_vector.push_ba

STL之容器适配器priority_queue的实现框架

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 在前面的文章STL之heap相关操作算法中介绍了堆的相关操作算法,由于堆的注意主要作用是用于排序,我们也知道堆排序的时间复杂度为o(nlogn),是一种不稳定的排序算法,利用堆这一数据结构,我们可以很快第获取一个大数据中最大(或最小)的k个数.同时,上篇文章中,也提出了相关heap算法的一些问题. 问题1:在调用push_heap函数实现向堆中插入元素之前,我们必须要先将向底层容器的末端插入该元素,然后才能调用push_heap内部的向上调整来

11.STL简单set和multiset的实现

set的特性是所有元素都会根据键值自动排序,set的元素不像map那样同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值.Set不允许两个元素拥有相同的键值.不能通过迭代器修改set元素的值. multiset和set的唯一区别在于multiset允许键值重复. 我们采用红黑树作为set和multiset的底层数据结构,set和multiset的实现完完全全是在红黑树的基础上封装的一层接口,所有的set和multiset操作都转而调用红黑树的API.有关STL红黑

STL Vector容器

STL Vector容器 Vector容器简介 vector是将元素置于一个动态数组中加以管理的容器.        vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,这个等下会详讲).         vector尾部添加或移除元素非常快速.但是在中部或头部插入元素或移除元素比较费时        头文件:#include<vector> vector对象的默认构造 vector采用模板类实现, vector对象的默认构造形式: vector<T>

c++复习:STL之容器

1 STL的string 1 String概念 string是STL的字符串类型,通常用来表示字符串.而在使用string之前,字符串通常是用char*表示的.string与char*都可以用来表示字符串,那么二者有什么区别呢. string和char*的比较 string是一个类, char*是一个指向字符的指针. string封装了char*,管理这个字符串,是一个char*型的容器. string不用考虑内存释放和越界. string管理char*所分配的内存.每一次string的复制,取