C++实现自定义vector以及allocator

Posted Redamanc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++实现自定义vector以及allocator相关的知识,希望对你有一定的参考价值。

实现vector

前言

不知道大家有没有这种感觉,学习了一段时间后,总想着自己能够实现一些标准库里已经提供的东西,比如说STL容器vector、比如数据类型string
一方面是对这个知识可以有进一步的一个认识和了解,另一方面也可以检验自己的掌握程度。
基于此,我们今天实现自定义的vector

初步实现

通过包含头文件#include <vector>转到文档,我们可以查看系统自带vector的实现:

主要包含了三个指针:

  1. 指向数组起始位置的指针;
  2. 指向数组中最后一个有效元素位置的后继位置;
  3. 指向数组空间的后继位置。

了解了这些后,我们就可以自定义vector了:
代码如下:

template<typename T>
class vector

public:
	vector(int size = 10)
	
		_first = new T[size];
		_last = _first;
		_end = _first + size;
	
	~vector()
	
		delete[]_first;
		_first = _end = _last = nullptr;
	
	vector(const vector<T>& rhs)
	
		int size = rhs._end - rhs._first;
		_first = new T[size];
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		
			_first[i] = rhs._first[i];
		
		_last = _first + len;
		_end = _first + size;
	
	vector<T>& operator=(const vector<T>& rhs)
	
		if (this == &rhs)
			return *this;

		delete[]_first;


		int size = rhs._end - rhs._first;
		_first = new T[size];
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		
			_first[i] = rhs._first[i];
		
		_last = _first + len;
		_end = _first + size;
		return *this;
	
	void push_back(const T& val) // 向容器末尾添加元素
	
		if (full())
			expand();
		*_last++ = val;  
	
	void pop_back() // 从容器末尾删除元素
	
		if (empty())
			return;
		--_last; 
	
	T back()const // 返回容器末尾的元素的值
	
		return *(_last - 1);
	
	bool full()const  return _last == _end; 
	bool empty()const  return _first == _last; 
	int size()const  return _last - _first; 
private:
	T* _first; // 指向数组起始的位置
	T* _last;  // 指向数组中有效元素的后继位置
	T* _end;   // 指向数组空间的后继位置

	void expand() // 容器的二倍扩容
	
		int size = _end - _first;
		T *ptmp = new T[2 * size];
		for (int i = 0; i < size; ++i)
		
			ptmp[i] = _first[i];
		
		delete[]_first;
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	
;

出现的问题

我们知道,对于一个容器来说,里面存放的数据类型可以是系统自带的默认类型,比如:intchar等等,也可以是自己定义的class XXX类型。
对于默认类型来说,我们上面的代码是没有问题的(可以自行验证),但是对于自定义类型来说,就会出现问题:
比如我们加上自己定义的简单类型,分别给构造函数、析构函数、拷贝构造函数都加上打印信息:

class Test

public:
	Test()  cout << "Test()" << endl; 
	~Test()  cout << "~Test()" << endl; 
	Test(const Test&)  cout << "Test(const Test&)" << endl; 
;

接下来运行代码:

int main()

	vector<Test> vec;

	return 0;

结果如下:

我们会发现,我只是定义了一个空的容器
但是它却自动给我添加了十个对象
这,,于情于理都说不过去。

解决问题

Allocator(空间配置器)

分析完问题后,我们可以继续查看系统自带vector是如何做的?

可以看到,系统的实现,除了数据类型外,还有一个allocator。而这个,就是解决问题的办法了!
我们再来回过头看看问题,其实问题主要出在了构造函数的new上:

对于new来说,它要做两件事情:

  1. 开辟空间;
  2. 数据初始化(对于自定义类型来说就是要调用构造函数

可问题是,我们既然选择把它作为容器,那么就只需要它提供一个场所(空间),至于这个场所里存放什么数据,是由程序员来决定的,不应该由容器来擅作主张。
那么,问题的关键就在于将开辟空间和构造对象分离开
而这,也就是空间配置器做的工作;
于是我们添加容器的空间配置器:

// 定义容器的空间配置器,和C++标准库的allocator实现一样
template<typename T>
struct Allocator

	T* allocate(size_t size) // 负责内存开辟
	
		return (T*)malloc(sizeof(T) * size);
	
	void deallocate(void* p) // 负责内存释放
	
		free(p);
	
	void construct(T* p, const T& val) // 负责对象构造
	
		new (p) T(val); // 定位new
	
	void destroy(T* p) // 负责对象析构
	
		p->~T(); // ~T()代表了T类型的析构函数
	
;

可以看到,空间配置器主要有四个功能:

  1. 内存开辟 allocate(底层调用malloc);
  2. 内存释放 deallocate(底层调用free);
  3. 对象构造 construct(调用构造函数);
  4. 对象析构 destroy(调用析构函数)。

并且,修改自定义的vector

template<typename T, typename Alloc = Allocator<T>>
class vector

public:
	vector(int size = 10)
	
		// 需要把内存开辟和对象构造分开处理
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	
	~vector()
	
		// 析构容器有效的元素,然后释放_first指针指向的堆内存
		for (T* p = _first; p != _last; ++p)
		
			_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
		
		_allocator.deallocate(_first); // 释放堆上的数组内存
		_first = _last = _end = nullptr;
	
	vector(const vector<T>& rhs)
	
		int size = rhs._end - rhs._first;
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		
			_allocator.construct(_first + i, rhs._first[i]);
		
		_last = _first + len;
		_end = _first + size;
	
	vector<T>& operator=(const vector<T>& rhs)
	
		if (this == &rhs)
			return *this;

		for (T* p = _first; p != _last; ++p)
		
			_allocator.destroy(p); // 把_first指针指向的数组的有效元素进行析构操作
		
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		
			_allocator.construct(_first + i, rhs._first[i]);
		
		_last = _first + len;
		_end = _first + size;
		return *this;
	
	void push_back(const T& val) // 向容器末尾添加元素
	
		if (full())
			expand();
			
		_allocator.construct(_last, val);
		_last++;
	
	void pop_back() // 从容器末尾删除元素
	
		if (empty())
			return;

		// 不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destroy(_last);
	
	T back()const // 返回容器末尾的元素的值
	
		return *(_last - 1);
	
	bool full()const  return _last == _end; 
	bool empty()const  return _first == _last; 
	int size()const  return _last - _first; 
private:
	T* _first; // 指向数组起始的位置
	T* _last;  // 指向数组中有效元素的后继位置
	T* _end;   // 指向数组空间的后继位置
	Alloc _allocator; // 定义容器的空间配置器对象

	void expand() // 容器的二倍扩容
	
		int size = _end - _first;
		T* ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; ++i)
		
			_allocator.construct(ptmp + i, _first[i]);
		
		for (T* p = _first; p != _last; ++p)
		
			_allocator.destroy(p);
		
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	
;

最终结果

修改完之后,我们再来运行如下代码:

int main()

	Test t1;
	cout << "-------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	cout << "-------------------" << endl;
	
	return 0;

想要实现的效果是:
用户定义了多少个对象,容器里面包含多少个对象;
容器不会自己定义多个对象出来。
运行结果如下:

可以看到,开辟空间和构造对象分离开了。

对比系统自带的

我们还可以使用系统自带的vector来对比:

#include <iostream>
#include <vector>
using namespace std;

class Test

public:
	Test()  cout << "Test()" << endl; 
	~Test()  cout << "~Test()" << endl; 
	Test(const Test&)  cout << "Test(const Test&)" << endl; 
;
int main()

	Test t1;
	cout << "-------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	cout << "-------------------" << endl;

	return 0;

运行结果:

可以看到,是一样的!

以上是关于C++实现自定义vector以及allocator的主要内容,如果未能解决你的问题,请参考以下文章

利用std::allocator实现自定义的vector类

通过自定义vector和string来理解move和forward

通过自定义vector和string来理解move和forward

c++ vector详解

自定义C++ STL内存分配器

c++为自定义矩阵类实现迭代器