[C ++ template]-STL vector container vector and container space allocator implementation

1. Simple implementation of STL vector container vector

1. Definition of container
First, let's understand what a container is. His written explanation is Baidu. Here we explain it in a popular way. A container is an object that holds other objects. And this object has methods to deal with other objects.

C ++ uses a template-based approach to processing containers, and containers in STL provide a variety of data structures.

2. The vector container
(1) is defined
as a linear sequential structure. Equivalent to an array, but its size can be unspecified and automatically expanded. It can be operated like an array. Due to its characteristics, we can completely treat vector as a dynamic array.

(2) Features

  • You can directly access any element.
  • Linear sequential structure. You can specify a continuous space, or you can not specify the size in advance, the space can be automatically expanded, or it can be operated like an array, that is, the [] operator and vector.at () are supported, so it can be regarded as a dynamic array, usually reflected in Add data push_back () and delete end data pop_back ().
  • When the allocated space is not enough, vector will apply for a larger memory block (increasing by a multiple of 2), then copy the original data to the new memory block and destroy the objects in the original memory block, and finally release the original memory space . Therefore, if the amount of data stored in the vector is very large, it will consume performance, so the performance is optimal when the size of the vector is known in advance.
  • save space. Because it is continuously stored, there is no waste in the area where the data is stored, but in fact most of the time it is not full, so the area that is not actually stored is wasted
  • The efficiency of inserting and deleting operations internally is low. Due to the sequential table structure design inside vector, such operations are basically prohibited. It is designed to only perform append and delete operations on the back end.
    His layout is as follows:
    Insert picture description here
    Let's take a look at his specific code implementation:
 template<typename T>
class vector
{
public:
	vector(int size = 10)
	{
		_first = new T[size];
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		delete[] _first;
		_first = _last = _end = 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;
	}
};
int main()
{
	vector<int> vec;
	for (int i = 0; i < 20; ++i)
	{
		vec.push_back(rand() % 100);
	}
	//vec.pop_back();
	while (!vec.empty())
	{
		cout << vec.back() << " ";
		vec.pop_back();
	}
	cout << endl;
	return 0;
} 

The results are as follows:
Insert picture description here

Second, the simple implementation of the container space adapter allocator

The above vector container implementation lacks something called the space allocator.
First, let's take a look at the vector defined in the c ++ standard library

template<class _Ty,
	class _Alloc = allocator<_Ty> >
	class vector
		: public _Vector_alloc<!is_empty<_Alloc>::value,
			_Vec_base_types<_Ty, _Alloc> >

Then let us think about what is wrong with the above vector container.
Question 1:
When we use the Test object to instantiate, what is the running result?
Test is implemented as follows:

class Test
{
public:
	Test(){cout << "Test()" << endl;}
	~Test(){cout << "~Test" << endl;}
};

The running result is as follows: the
Insert picture description here
meaning of the running result:
because when we write the constructor, the size is 10, in this place, he is an empty container, but he has constructed 10 Test objects and destructed 10 times. Because new is used in the construction, it will not only open up space, but also construct objects , so 10 objects are constructed

wrong reason:

  1. When defining a container object, the bottom layer is only for space development, not for constructing objects. But if you use new, it will accomplish both things at the same time.
  2. Delete was used during the destructuring, and each element of the array pointed to by the _first pointer was destructed as a valid test object. The array may be very long, but there may be only a few valid elements. When we destruct, we only destruct the valid elements, and then release the entire array memory.

If you think about it carefully, there are still some problems with the above code.
Problem 2:
Execute the following code:

int main()
{
	Test t1, t2, t3;
	cout << "-------------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	vec.push_back(t2);
	vec.push_back(t3);
	cout << "-------------------------" << endl;
	vec.pop_back();
	cout << "-------------------------" << endl;
	
	return 0;
}

The results of the operation are as follows:
Insert picture description here
The meaning of the operation results: the
bottom uses new, and the test object is actually placed in each position. When push_back is equivalent to assigning a value to the existing test object, a problem occurs.

But you can't use delete to destruct. Delete not only calls the destructor, but also does a free operation. We should only destruct and not release the array heap memory.

Solution:

1. Separate memory development and object construction separately
2. Destruct the effective elements of the container, and then release the heap memory pointed to by the _first pointer
3. Only need to destruct the object, separate the object's destruction and memory

The space allocator of the container: The core function of the space allocator is to decompose the process of object memory development and object construction, and the process of object destructuring and memory release. The development of the underlying memory of the container, memory release, object construction and destruction are all achieved through the allocator space adapter.
The specific code is implemented as follows:

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 destory(T* p)//负责对象析构
	{
		p->~T();//~T()代表了T类型的析构函数
	}
};
/*
容器低沉内存开辟,内存释放,对象构造析构,都通过allocator空间配置器来实现
*/
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 = 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();
		_allocator.construct(_last, val);
		_last++;
	}
	void pop_back()//从容器末尾删除元素
	{
		if (empty())
			return;
		//--_last;不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destory(_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 = new T[2 * size];
		T* ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; ++i)
		{
			_allocator.construct(ptmp + i, _first[i]);
			//ptmp[i] = _first[i];
		}
		delete[]_first;
		for (T* p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};
class Test
{
public:
	Test(){ cout << "Test()" << endl; }
	~Test() { cout << "Test()" << endl; }

};
Published 98 original articles · won praise 9 · views 3670

Guess you like

Origin blog.csdn.net/qq_43412060/article/details/105123315