[C + + from bronze to king] Part 10: simulation and implementation of vector class of STL

Posted by bbauder on Wed, 19 Jan 2022 10:46:49 +0100

Catalogue of series articles

preface

1, Depth analysis and Simulation of vector

1. Simulation implementation of the core interface of vector

namespace yyw
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	public:
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		T& operator[](size_t i)
		{
			return _start[i];
		}

		//The first const cannot be written and can only be read. The latter const member function is because the const object can only call the const member function
		//Non const objects can call either non const member functions or const member functions
		const T& operator[](size_t i) const   
		{
			return _start[i];
		}

		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _endofstorage - _start;
		}
		bool empty()
		{
			return (_finish - _start == 0);
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();                      //Problem of preventing iterator failure
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*size());   
					//memcpy and strcpy cannot be empty when copying for the first time. memcpy cannot be used to copy objects. Copy by byte and shallow copy
					for (size_t i = 0; i < oldsize; i++)
					{
						tmp[i] = _start[i];   //Called is a deep copy of T operator =
					}
					delete[] _start;
				}
				_start = tmp;
				//_finish = tmp + size();
				//_endofstorage = tmp + capacity();
				_finish = tmp + oldsize;
				_endofstorage = tmp + n;
			}
		}
		void resize(size_t n, const T& value = T())
		{
			if (n <= size())                    //When n is smaller than the original size, the data is reduced to n
			{
				_finish = _start + n;
				return;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}  //memset cannot initialize a user-defined type. It must be assigned one by one
				iterator it = _finish;
				_finish = _start + n;
				while (it != _finish)
				{
					*it = value;
					it++;
				}
			}
		}

		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 2:capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(_start < _finish);
			_finish--;
		}
		iterator insert(iterator pos, const T& x)
		{
			assert(pos <= _finish);       //=_ finish is tail insertion
			if (_finish == _endofstorage)
			{
				size_t n = pos - _start; //Prevent iterator failure

				size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;

				reserve(newcapacity);
				pos = _start + n;  //Prevent iterator failure
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(pos < _finish);
			iterator it = pos;   
			while (it < _finish)  //Finally, move it to_ finish went up
			{
				*it = *(it + 1);
				it++;
			}
			_finish--;
			return pos;
		}



		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

		~vector()
		{
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}
		//v2(v1)
		vector(const vector<T>& v)     //copy construction 
		{
			_start = new T[v.capacity()];
			_finish = _start;
			_endofstorage = _start + v.capacity();
			for (size_t i = 0; i < v.size(); i++)
			{
				*_finish = v[i];
				_finish++;
			}
		}

		//v2(v1) initializes v2 to null, traverses v1, and inserts v1 into v2;
		//vector(vector<T>& v)
		//	:_start(nullptr)
		//	, _finish(nullptr)
		//	, _endofstorage(nullptr)
		//{
		//	for (auto& e : v)
		//	{
		//		reserve(v.capacity());
		//		push_back(e);
		//	}
		//}
		
		//s2=s1;  // Assignment operator overload
		void Swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

		//v3=v1;
		vector<T>& operator=(vector<T>& v)
		{
			//Swap(v);
			//return *this;
			if (this != &v)   //The assignment operator overload should check whether it assigns its own value
			{
				delete[] _start;
				_start = new T[v.capacity()];
				memcpy(_start, v._start, sizeof(vector<T>)*size());
			}
			return *this;
		}
	//private:
	//	T* _a;
	//	size_t size;
	//	size_t capacity;
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

Problems encountered:

  • When resize opens up a valid data space and initializes, memset initialization cannot be used because mem series functions are processed by bytes.
  • When reserving a capacity space, the new space opened cannot use memcpy to copy the data of the old space, but to use circular assignment, because memcpy is a shallow copy. The shallow copy will eventually lead to the release of the old space, and the pointer of the new space will point to a space that has been released, resulting in program crash

2. Core interface test of vector

void testvector1()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.pop_back();
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			//*it += 1;
			std::cout << *it << " ";
			it++;
		}
		std::cout << std::endl;
		for (size_t i = 0; i < v1.size(); i++)
		{
			v1[i] -= 1;
			std::cout << v1[i] << " ";
		}
		std::cout << std::endl;
		for (auto& e : v1)          //The range for is supported if iterators are supported
		{ 
			e -= 1;
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}
	void testvector2()
	{
		const vector<int> v2;     //vector of const object
		vector<int>::const_iterator vit = v2.begin();
		while (vit != v2.end())
		{
			//*vit += 1;
			std::cout << *vit << " ";
		}
		std::cout << std::endl;
	}
	void testvector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.insert(v1.begin() + 1, 10);
		v1.erase(v1.begin() + 1);
		for (auto e : v1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}
	void testvector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		v1.push_back(6);

		std::cout << v1.size() << std::endl;
		std::cout << v1.capacity() << std::endl;
		v1.resize(10);
		v1.reserve(20);
		for (auto& e : v1)          //The range for is supported if iterators are supported
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		std::cout << v1.size() << std::endl;
		std::cout << v1.capacity() << std::endl;

		v1.resize(12);
		v1.reserve(30);
		for (auto& e : v1)          //The range for is supported if iterators are supported
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		std::cout << v1.size() << std::endl;
		std::cout << v1.capacity() << std::endl;
	}
	void testvector5()
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(9);
		v1.push_back(8);
		v1.push_back(7);
		v1.push_back(6);
		vector<int>v2(v1);
		for (auto& e : v1)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		for (auto& e : v2)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
		vector<int> v3;
		v3 = v1;
		for (auto& e : v2)
		{
			std::cout << e << " ";
		}
		std::cout << std::endl;
	}

Test documents:

#define _CRT_SECURE_NO_WARNINGS   1
#include"vector.h"
int main()
{
	//yyw::testvector1();
	//yyw::testvector2();
	//yyw::testvector3();
	//yyw::testvector4();
	yyw::testvector5();
	return 0;
}

3. Copy problems using memcpy

Assuming that memcpy is used for copying in the reserve interface in the vector implemented by the simulation, what will happen to the following code?

int main()
{
	bite::vector<bite::string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	return 0;
}
  • Problem analysis:
  • memcpy is a binary format copy of memory, which copies the contents of one memory space intact to another memory space.
  • If you copy a custom type element, memcpy is efficient and will not make an error. However, if you copy a custom type element and resource management is involved in the custom type element, an error will occur, because the copy of memcpy is actually a shallow copy.

4. Dynamic two-dimensional array understanding

// Take the first n behavior of Yang Hui triangle as an example: suppose n is 5
void test5(size_t n)
{
	// Use vector to define the two-dimensional array vv. Each element in VV is vector < int >
	yyw::vector<yyw::vector<int>> vv(n);
	// Set all the elements in vecotr < int > in each row of the two-dimensional array to 1
	for (size_t i = 0; i < n; ++i)
		vv[i].resize(i + 1, 1);
	// Assign values to all elements of the first column and diagonal of Yang Hui's triangle
	for (int i = 2; i < n; ++i)
	{
		for (int j = 1; j < i; ++j)
		{
			vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
		}
	}
}

yyw::vector<yyw::vector> vv(n); Construct a VV dynamic two-dimensional array. There are a total of n elements in vv. Each element is of vector type, and each line does not contain any elements. If n is 5, it is as follows:

After the elements in vv are filled:

When using the vector in the standard library to build a dynamic two-dimensional array, it is consistent with the above figure.

summary

The above is what we want to talk about today. This paper introduces the common copy problems and Simulation Implementation of the vector class, and the vector class provides a large number of functions and methods that enable us to process data quickly and conveniently, which is very convenient, so we must master it. In addition, if there are any problems above, please understand my brother's advice, but it doesn't matter. It's mainly because I can insist. I hope some students who study together can help me correct them. However, if you can be gentle, please tell me that love and peace are the eternal theme and love you.

Topics: C++ STL vector