Article directory
Implemented functions
Simulation implementation
Since the string was implemented earlier, the implementation process is not the focus here, and the focus is on the iterator invalidation and copying issues
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
// constructor
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorge(nullptr)
{
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _endofstorge(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
vector(size_t n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(vector<T>& v)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
// destructor
~vector()
{
delete[] _start;
_start = _finish = _endofstorge = nullptr;
}
// operator=
vector<T>& operator=(vector<T>& v)
{
//_start = new T[v.capacity()];
//_finish = _start + v.size();
//_endofstorge = _start + v.capacity();
swap(v);
return *this;
}
// capacity
size_t size()
{
return _finish - _start;
}
void resize(size_t num, const T& val = T())
{
if (num < size())
{
_finish = _start + num;
}
else
{
while (_finish != _start + num)
{
push_back(val);
_finish++;
}
}
}
size_t capacity()
{
return _endofstorge - _start;
}
void reserve(size_t num)
{
if (num > capacity())
{
T* tmp = new T[num];
size_t sz = size();
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorge = _start + num;
}
}
bool empty()
{
if (_start == _finish)
return true;
return false;
}
// element access
T& operator[](size_t pos)
{
return *(_start + pos);
}
// iterator
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// modifier
void push_back(T data)
{
if (_finish == _endofstorge)
{
size_t sz = size();
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
T* tmp = new T[newcapacity];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_finish = tmp + sz;
_endofstorge = tmp + newcapacity;
_start = tmp;
}
assert(_finish);
*(_finish) = data;
++_finish;
}
void pop_back()
{
--_finish;
}
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorge)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorge, v._endofstorge);
}
private:
iterator _start;
iterator _finish;
iterator _endofstorge;
};
iterator invalidation
First of all, it is necessary to be clear about what is an iterator invalidation, and where is the application scenario of an iterator invalidation?
Here is an example. The iterator may fail during the insert process. The specific reason for the failure is:
The iterator itself can be understood as a pointer and the space pointed to by the pointer is fixed, so when using a pointer here, if the original space is destroyed due to expansion, then the content pointed to by the iterator is actually the same It doesn't make any sense, which is why the iterator fails
It will be easier to understand with the following example:
void test_vector1()
{
vector<int> v(5,5);
auto it = v.begin();
v.insert(it, 10);
v.insert(it, 10);
v.insert(it, 10);
v.insert(it, 10);
v.insert(it, 10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
A vector is defined here, and there are 5 numbers 5 in it. At this time, _start
the space pointed to is an area, so the head of the container can be found by using an iterator here, and it is very convenient to insert
But when the data is inserted, _start
the space pointed to changes, but the iterator here still points to the original position. In this case, it is a classic iterator failure problem. Therefore, under some compilers, the standard library If the iterator is insert/erase
used, the iterator must be checked forcibly and cannot be used
Under some compilers, the check will not be performed, so the iterator will fail. Based on this situation, when using the iterator, pay attention to whether there is a possibility of failure
Implicit shallow copy problem
First look at the following code
void push_back(T data)
{
if (_finish == _endofstorge)
{
size_t sz = size();
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
T* tmp = new T[newcapacity];
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_finish = tmp + sz;
_endofstorge = tmp + newcapacity;
_start = tmp;
}
assert(_finish);
*(_finish) = data;
++_finish;
}
If you memcpy
have doubts about whether it can be used, you can see the following test function
void test_vector1()
{
vector<string> v;
v.push_back("111111111111111111111");
v.push_back("111111111111111111111");
v.push_back("111111111111111111111");
v.push_back("111111111111111111111");
v.push_back("111111111111111111111");
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
If memcpy
the result of using is like this
The reason for such a result is also very simple. This is because it memcpy
is actually a shallow copy in essence. Its working principle is to copy the content of each byte, so this is actually a shallow copy. Use the following mechanism To explain it can be seen as:
Therefore, we have adopted an improved version of the copy mechanism to allow these data to be copied to a certain extent
In this way, the effect of copying can be achieved