[C++STL] Simulate the implementation of vector containers


foreword

This article takes you into the simulation implementation of vector. For vector, it is a necessary way for us to learn STL in depth.


1. Member functions of vector

Depending on how the library is implemented, the member functions are as follows:

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

From c++11, default values ​​​​can be used for member variables, and here we can use default values.

2. Addition, deletion, inspection and modification work

Explain size() and capapcity()

insert image description here

The size of size is _finish - _start
The size of capacity is _end_of_storage - _start

2.1reserve()

The function of this function is: expansion.

void reserve (size_type n);
  • Ideas:
  • 1. If the size n to be expanded is smaller than capacity, it will not shrink.
  • 2. Otherwise, perform capacity expansion.
    • First apply for a space of n size
    • Copy the data in the original space to the new space, and release the original space
    • Hand over the data of the new space to _start for management.
//扩容
void reserve(size_t n)
{
    
    
	//防止有些情况是直接调用
	if (n > capacity())
	{
    
    
		size_t oldsize = size();//必须要记录旧的size,旧的size是用旧的_finish - _start获得的
		iterator tmp = new T[n];
		//memcpy(tmp, _start, sizeof(T) * size());
		//这里如果使用memcpy函数的话,如果vector对象是自定义类型,比如string,会造成浅拷贝问题。
		for (size_t i = 0; i < oldsize;i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
		{
    
    
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		//_finish = _start + size();
		//如果这样用的话,size()是用旧的_finish - 新的_start,不是连续的空间,没有意义
		_finish = _start + oldsize;//_finish必须更新,旧的_finish迭代器已经失效了
		_end_of_storage = _start + n;
	}
	//tmp不用处理,出了作用域自动调用析构
}

注意:在扩容时,如果是自定义类型,比如string,使用memcpy函数进行拷贝数据,会出现浅拷贝问题。
insert image description here
v1 will be copied to v2, and the pointer of v2 still points to the space pointed to by the string object of v1. This creates the problem of calling the destructor twice to free the same space.
The correct solution is:
call the assignment operator overload of string when copying to complete the deep copy.

insert image description here

Another point is:

Note: There will be an iterator invalidation problem here.

insert image description here

At this point the space is full, you need to re-apply for space.

insert image description here

After re-applying for space, both _start and _end_of_stortage will point to the new space, but _finish still points to the old space that has been released. If you update the _finish position like this:

_finish = _start + size();

The size() here is obtained from the old _finish - new _start, which is meaningless.

Solution:
save the old size, then _start plus the old size is the position of _finish.

2.2 resize()

The function of this function is: re-change the size of the vector container, let it reach n. The new space is initialized to val by default
:

  • 1. If n is less than size, let _finish point to _start + n position.
  • 2. If n is greater than or equal to size, first call the reserve function to expand the capacity, and then fill the new space with val.
void resize(size_t n, const T& val = T())
{
    
    
	if (n < size())
	{
    
    
		_finish = _start + n;
	}
	else
	{
    
    
		//扩容到n,如果n<capacity(),则啥事不做,否则,扩容到n
		reserve(n);
		//将多的空间全部设置成缺省值
		while (_finish != _start + n)
		{
    
    
			*_finish = val;
			++_finish;
		}
	}
}

Note that the default value here is T(), which is an anonymous object of type T.
No matter what T is, you can use the template to instantiate a specific anonymous object, even if it is a built-in type, such as int, you can also instantiate int(), where the value is 0

2.3 insert()

insert image description here
Here we only implement the most commonly used interfaces.

What this function does is:

Inserts the template object val at position pos.

Ideas:

  • Check capacity before inserting elements
  • If the capacity is full, call the reserve function to perform capacity expansion
  • Then start from the pos position and move all the elements after it back.
  • Then insert val at pos position.
void insert(iterator pos, const T& val)
{
    
    
	assert(pos <= _finish);
	//插入前先检查扩容
	if (_finish == _end_of_storage)
	{
    
    
		size_t oldpos = pos - _start;//记录相对位置
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);

		pos = _start + oldpos;
	}

	//记录旧的size,防止出现迭代器失效问题。

	//将pos之后的位置往后挪,再插入
	iterator end = _finish - 1; // reserve之后,_finish也跟着更新了。
	while (end >= pos)
	{
    
    
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	_finish += 1;
}

Note: Insert will still have the problem of iterator invalidation during capacity expansion.
Solution: Record the relative position of pos in advance, that is, oldpos = pos - _start;
then after the expansion is completed, update the position of pos, pos = _start + oldpos;
otherwise, the old pos iterator will be invalid after expansion.

2.4 erase()

The function of this function is: delete the value of pos position.

//在删除最后一个位置或者只剩下一个待删除元素时,会出现迭代器失效问题。
iterator erase(iterator pos)
{
    
    
	assert(pos < _finish);
	iterator end = pos;
	while (end != _finish)
	{
    
    
		*end = *(end + 1);
		++end;
	}
	--_finish;

	return pos;
}

There is one point to note: when we delete an element, the pos iterator will become invalid, because the space has been released, and the pos iterator cannot be used at this time.
insert image description here

Solution: Return the position of the next element of the deleted position, which is the pos position.
However, the pos iterator cannot be used when there is only one element left to delete or when the element at the last position is deleted.

2.5 push_back()和pop_back()

As the name suggests, the function of these two functions is to insert an element at the last position and delete the last position.
Just reuse the insert and erase functions directly, so I won’t repeat them here.

3. [] overloading and iterators

typedef T* iterator;
typedef const T* const_iterator;

3.1begin iterator

Just return the _start address.

iterator begin()
{
    
    
	return _start;
}

const_iterator begin() const
{
    
    
	return _start;
}

3.2 end iterator

Just return the _finish address.

iterator end()
{
    
    
	return _finish;
}

const_iterator end() const
{
    
    
	return _finish;
}

3.3 [] operator overloading

Idea: Just give the pos subscript.
Before that, it is necessary to judge that the pos position cannot exceed the size of the entire sequence table.

T& operator[]( size_t pos)
{
    
    
	assert(pos < size());
	return *(_start + pos);
}

//不可修改的
const T& operator[]( size_t pos) const
{
    
    
	assert(pos < size());
	return *(_start + pos);
}

Fourth, the default member function

4.1 Constructor

There are three types of constructors:

  • No parameter construction
  • Construct n val objects
  • Constructed from iterated intervals

For no-argument construction, we only need to initialize member functions.

  • 1. No parameter construction
//无参构造函数
vector()
//:_start(nullptr)
//,_finish(nullptr)
//,_end_of_storage(nullptr)
{
    
    }
  • 2. Construct n val objects
//构造n个对象
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
    
    
	//1.开空间
	reserve(n);
	//2.将空间填充为val对象
	for (size_t i = 0; i < capacity(); i++)
	{
    
    
		_start[i] = val;
	}
	//也可以复用resize
	//resize(n, val);
}

Note: It must be initialized during construction. If it is not initialized, there will be a problem of accessing wild pointers when calling the reserve function to open space.
You can also call the resize function to copy and construct n objects.

  • 3. Use iterative intervals for construction

Similarly, if it is not initialized, wild pointers will be accessed after capacity expansion.

template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{
    
    
	while (first != last)
	{
    
    
		push_back(*first);
		++first;
	}
}

4.2 Copy constructor

  • 1. Copy construction traditional writing method
//拷贝构造,必须实现深拷贝
//传统写法
//v1(v)
vector(const vector<T>& v)
	//这里必须初始化,否则扩容时会访问不属于自己的空间
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
	//如果私有成员给了缺省值,就不用再再次初始化了
{
    
    
	reserve(v.capacity());
	//memcpy(_start, v._start, sizeof(T) * v.size());
	//这里使用memcpy的错误跟扩容的错误一样,不再赘述
	for (size_t i = 0; i < v.size(); i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
	{
    
    
		_start[i] = v._start[i];
	}

	_finish = _start + v.size();
}

Several issues that need to be paid attention to in copy construction:

  • 1. The copy construction is the same as the constructor. If it is not initialized, a wild pointer will be accessed when the space is opened.
  • 2. The copy construction must perform a deep copy, otherwise, if the object of the vector is a custom type, such as string, there will be a problem of freeing the same space twice.

4.3 Destructors

Release the space pointed to by _start, and then set it all empty.

~vector()
{
    
    
	if (_start)
	{
    
    
		delete[] _start;
		_start = _finish = _end_of_storage;
	}
}

Summarize

This is the end of the simulation implementation of vector. If it is helpful to you, you may wish to pay attention!

Guess you like

Origin blog.csdn.net/w2915w/article/details/131743540
Recommended