实现自定义容器——vector
·
什么是vector
- 容器是
STL中的一个重要概念,它们封装了一些常用的数据结构,可以提高程序的编写速度,方便程序员使用。 - 前面我们介绍了
vector的基本使用,接下来我们来实现一个自定义的vector,方便我们更好的理解这个容器 - 对
vector的接口的实现参考cppreference-vector
准备工作
- 这里是对
vector的介绍,vecctor是一个封装了动态数组的序列容器
- 如果你学过数据结构的话,动态数组是使用指针模拟的,而动态数组会涉及到扩容的问题,既然
vector也是使用动态数组封装的,那么必定是需要扩容的,扩容后还需要把现有元素移动到新空间等操作,为了方便,我们可以实现一些工具函数,减少重复代码。 - 我们实现的是接受一个模板参数的
vector,标准库的vector还有一个 空间分配器- 需要哪些成员变量(类型都为
T*),有了前两个变量我们可以O(1)的知道容器内有效元素的个数,有了最后一个变量我们可以方便的知道什么时候该扩容了- 动态数组的起始地址
- 最后一个有效元素的下一个位置
- 最后一个可用空间的下一个位置
- 模板参数
T比较抽象,我们不知道他是什么意思,我们来封装一些意义明确的数据类型,参考官网即可
- 需要哪些成员变量(类型都为
- 封装是面向对象的概念,实现自然少不了类,
vector也有各种不同的构造函数,有的构造函数需要预分配空间,所以需要分配空间,我们实现的vector暂时不需要使用分配器,我们使用new来实现空间的分配,有的构造函数还支持对空间内的元素进行初始化

- 分析得到需要分配空间、初始化空间内的元素为指定的元素的函数
void initialize_fill(size_type count, const T& value)
{
start = allocate_storage(count);
finish = start;
capacity_ = start + count;
fill_n(start, count, value);
finish = start + count;
}
static T* allocate_storage(size_type count)
{
return count == 0 ? nullptr : new T[count];
}
static void fill_n(T* dest, size_type count, const T& value)
{
while (count--)
{
*dest = value;
++dest;
}
}
具体实现
- 首先我们实现最基本的构造函数和析构函数,
C++11后还引入了移动构造函数和移动赋值重载函数,改变参数类型为右值引用即可 - 这里还需要增加一些工具函数,移动构造函数会进行资源窃取而非拷贝,所以需要一个交换双方资源的函数,窃取后需要对被移动的对象的资源置空,还需要一个重置函数, 使用另一个容器构造一个新的容器时,需要拷贝对方的资源,需要一个拷贝函数
- 工具函数
void copy_from(const vector& other)
{
size_type other_size = other.size();
if (other_size == 0)
return;
start = allocate_storage(other_size);
finish = start;
capacity_ = start + other_size;
copy_range(other.start, other.finish, start);
finish = start + other_size;
}
void reset_storage() noexcept
{
start = nullptr;
finish = nullptr;
capacity_ = nullptr;
}
static void copy_range(const T* first, const T* last, T* dest)
{
while (first != last)
{
*dest = *first;
++first;
++dest;
}
}
void swap(vector& other) noexcept
{
std::swap(start, other.start);
std::swap(finish, other.finish);
std::swap(capacity_, other.capacity_);
}
- 完整构造函数实现(实现重载赋值运算符的时候注意检查自我赋值)
vector() noexcept : start(nullptr), finish(nullptr), capacity_(nullptr) {}
vector(int n, const T& value)
: start(nullptr), finish(nullptr), capacity_(nullptr)
{
if (n <= 0)
return;
initialize_fill(static_cast<size_type>(n), value);
}
vector(const vector& other)
: start(nullptr), finish(nullptr), capacity_(nullptr)
{
copy_from(other);
}
vector(vector&& other) noexcept
: start(other.start), finish(other.finish), capacity_(other.capacity_)
{
other.reset_storage();
}
~vector()
{
delete[] start;
}
vector& operator=(const vector& other)
{
if (this != &other)
{
vector tmp(other);
swap(tmp);
}
return *this;
}
vector& operator=(vector&& other) noexcept
{
if (this != &other)
{
delete[] start;
start = other.start;
finish = other.finish;
capacity_ = other.capacity_;
other.reset_storage();
}
return *this;
}
- 接下来实现元素的插入,插入元素涉及到扩容和移动元素,工具函数,当
vector存储的事非平凡类型时,使用move可以调用对方的移动赋值重载,提高效率。需要特别处理容器容量为0的时候,否则会出现段错误
void shift_right(iterator pos, size_type count)
{
iterator src = finish;
while (src != pos)
{
* (src + count - 1) = std::move(*(src - 1));
--src;
}
}
void ensure_capacity(size_type required_capacity)
{
if (required_capacity <= capacity())
return;
size_type new_capacity = capacity() == 0 ? 1 : capacity();
while (new_capacity < required_capacity)
new_capacity *= 2;
reallocate(new_capacity);
}
push_back和insert实现,需要注意插入后移动finish的位置,让其始终指向有效元素的下一个位置,insert返回的是插入位置的迭代器,push_back一般按照2倍扩容
void push_back(const T& value)
{
ensure_capacity(size() + 1);
*finish = value;
++finish;
}
void push_back(T&& value)
{
ensure_capacity(size() + 1);
*finish = std::move(value);
++finish;
}
iterator insert(const_iterator pos, const T& value)
{
size_type index = position_index(pos);
ensure_capacity(size() + 1);
iterator insert_pos = start + index;
shift_right(insert_pos, 1);
*insert_pos = value;
++finish;
return insert_pos;
}
iterator insert(const_iterator pos, T&& value)
{
size_type index = position_index(pos);
ensure_capacity(size() + 1);
iterator insert_pos = start + index;
shift_right(insert_pos, 1);
*insert_pos = std::move(value);
++finish;
return insert_pos;
}
iterator insert(const_iterator pos, size_type count, const T& value)
{
size_type index = position_index(pos);
if (count == 0)
return start + index;
ensure_capacity(size() + count);
iterator insert_pos = start + index;
shift_right(insert_pos, count);
fill_n(insert_pos, count, value);
finish += count;
return insert_pos;
}
- 接下来实现一些和容器容量和访问的函数,这里使用
std::reverse_iterator实现反向迭代器
T* data() noexcept { return start; }
const T* data() const noexcept { return start; }
iterator begin() noexcept { return start; }
iterator end() noexcept { return finish; }
const_iterator begin() const noexcept { return start; }
const_iterator end() const noexcept { return finish; }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); }
const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
T& front() { return *start; }
T& back() { return *(finish - 1); }
const T& front() const { return *start; }
const T& back() const { return *(finish - 1); }
bool empty() const noexcept { return finish == start; }
size_type size() const noexcept { return static_cast<size_type>(finish - start); }
size_type capacity() const noexcept { return static_cast<size_type>(capacity_ - start); }
T& operator[](size_type index) { return start[index]; }
const T& operator[](size_type index) const { return start[index]; }
const T& at(size_type index) const
{
if (index >= size())
throw std::out_of_range("vector::at");
return start[index];
}
T& at(size_type index)
{
if (index >= size())
throw std::out_of_range("vector::at");
return start[index];
}
- 接下来实现和删除有关的函数,删除元素后需要将指定位置后的元素向左移动,
erase函数需要注意如果要删除的位置大于end(),直接返回end(),如果符合要求则需要返回被删除元素的下一个位置的迭代器,注意clear函数并不会改变容量,只会清空元素
void shift_left(iterator from, size_type count)
{
iterator dest = from - count;
while (from != finish)
{
*dest = std::move(*from);
++dest;
++from;
}
}
void pop_back()
{
if (!empty())
--finish;
}
iterator erase(iterator pos)
{
if (pos < begin() || pos >= end())
return end();
shift_left(pos + 1, 1);
--finish;
return pos == finish ? end() : pos;
}
iterator erase(const_iterator pos)
{
if (pos < begin() || pos >= end())
return end();
return erase(start + (pos - begin()));
}
void clear() noexcept
{
finish = start;
}
- 测试代码
#include "vector.hpp"
#include <cassert>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
using my_vector::vector;
template <class T>
void expect_sequence(const vector<T>& vec, std::initializer_list<T> expected)
{
assert(vec.size() == expected.size());
std::size_t index = 0;
for (const auto& value : expected)
{
assert(vec[index] == value);
++index;
}
}
void test_default_constructor()
{
vector<int> vec;
assert(vec.empty());
assert(vec.size() == 0);
assert(vec.capacity() == 0);
assert(vec.begin() == vec.end());
}
void test_fill_constructor()
{
vector<int> vec(4, 7);
assert(!vec.empty());
assert(vec.size() == 4);
assert(vec.capacity() == 4);
expect_sequence(vec, {7, 7, 7, 7});
}
void test_push_back_and_element_access()
{
vector<std::string> vec;
vec.push_back("a");
vec.push_back(std::string("b"));
vec.push_back("c");
assert(vec.size() == 3);
assert(vec.front() == "a");
assert(vec.back() == "c");
assert(vec[1] == "b");
const vector<std::string>& cvec = vec;
assert(cvec.front() == "a");
assert(cvec.back() == "c");
assert(cvec[2] == "c");
}
void test_at()
{
vector<int> vec;
vec.push_back(10);
vec.push_back(20);
assert(vec.at(0) == 10);
assert(vec.at(1) == 20);
bool thrown = false;
try
{
(void)vec.at(2);
}
catch (const std::out_of_range&)
{
thrown = true;
}
assert(thrown);
}
void test_reserve_and_resize()
{
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.reserve(10);
assert(vec.capacity() >= 10);
expect_sequence(vec, {1, 2});
vec.resize(5);
assert(vec.size() == 5);
assert(vec[0] == 1);
assert(vec[1] == 2);
assert(vec[2] == 0);
assert(vec[3] == 0);
assert(vec[4] == 0);
vec.resize(3);
expect_sequence(vec, {1, 2, 0});
}
void test_insert_single()
{
vector<int> vec;
vec.push_back(1);
vec.push_back(3);
auto it = vec.insert(vec.begin() + 1, 2);
assert(it == vec.begin() + 1);
assert(*it == 2);
expect_sequence(vec, {1, 2, 3});
auto end_it = vec.insert(vec.end(), 4);
assert(end_it == vec.end() - 1);
expect_sequence(vec, {1, 2, 3, 4});
}
void test_insert_multiple()
{
vector<std::string> vec;
vec.push_back("head");
vec.push_back("tail");
auto it = vec.insert(vec.begin() + 1, 3, std::string("mid"));
assert(it == vec.begin() + 1);
expect_sequence<std::string>(vec, {"head", "mid", "mid", "mid", "tail"});
}
void test_pop_back_and_clear()
{
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.pop_back();
expect_sequence(vec, {1, 2});
std::size_t old_capacity = vec.capacity();
vec.clear();
assert(vec.empty());
assert(vec.size() == 0);
assert(vec.capacity() == old_capacity);
vec.push_back(9);
expect_sequence(vec, {9});
}
void test_erase()
{
vector<int> vec;
for (int i = 1; i <= 5; ++i)
vec.push_back(i);
auto it = vec.erase(vec.begin() + 2);
assert(it == vec.begin() + 2);
assert(*it == 4);
expect_sequence(vec, {1, 2, 4, 5});
auto end_it = vec.erase(vec.end() - 1);
assert(end_it == vec.end());
expect_sequence(vec, {1, 2, 4});
auto invalid = vec.erase(vec.end());
assert(invalid == vec.end());
}
void test_copy_semantics()
{
vector<std::string> original;
original.push_back("a");
original.push_back("b");
vector<std::string> copy(original);
vector<std::string> assigned;
assigned = original;
original[0] = "changed";
expect_sequence<std::string>(copy, {"a", "b"});
expect_sequence<std::string>(assigned, {"a", "b"});
expect_sequence<std::string>(original, {"changed", "b"});
}
void test_move_semantics_and_swap()
{
vector<std::string> source;
source.push_back("x");
source.push_back("y");
vector<std::string> moved(std::move(source));
expect_sequence<std::string>(moved, {"x", "y"});
assert(source.empty());
vector<std::string> other;
other.push_back("left");
other.push_back("right");
moved.swap(other);
expect_sequence<std::string>(moved, {"left", "right"});
expect_sequence<std::string>(other, {"x", "y"});
vector<std::string> assigned;
assigned = std::move(moved);
expect_sequence<std::string>(assigned, {"left", "right"});
assert(moved.empty());
}
void test_iterators()
{
vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
int sum = 0;
for (auto it = vec.begin(); it != vec.end(); ++it)
sum += *it;
assert(sum == 6);
const vector<int>& cvec = vec;
auto cit = cvec.cbegin();
assert(*cit == 1);
assert(*(cvec.cend() - 1) == 3);
auto rit = vec.rbegin();
assert(*rit == 3);
++rit;
assert(*rit == 2);
auto crit = cvec.crbegin();
assert(*crit == 3);
}
int main()
{
test_default_constructor();
test_fill_constructor();
test_push_back_and_element_access();
test_at();
test_reserve_and_resize();
test_insert_single();
test_insert_multiple();
test_pop_back_and_clear();
test_erase();
test_copy_semantics();
test_move_semantics_and_swap();
test_iterators();
std::cout << "all tests passed\n";
return 0;
}
迭代器失效
- 通过自定义
vector,你应该了解了vector的原理,接下来让我们探讨一个比较坑的问题——迭代器失效。什么会导致其失效呢?我们的实现中出现了扩容的操作,这就是一个迭代器失效的问题,扩容后原来的元素都指向了新的内存, 但是现存的迭代器指向的还是旧的内存,此时再去访问就会导致出现未定义行为;再比如 insert 插入后会导致部分元素移动位置,就会出现迭代器失效。 - 从上面我们可以推出,只要涉及到了元素移动,那么迭代器失效就会发生,下面是官网对迭代器失效的场景的介绍

- 迭代器失效的危害
- 原来的内存已经不被当前对象持有,访问可能会出现段错误或者程序崩溃的问题
- 可能某个结果是对的,让你误以为其它的结果也是对的,可能导致未预期的结果
- 如何预防:
- 尽量不要使用迭代器遍历容器,如果使用,遍历的时候不要做读以外的操作;或者必须接收删除、插入等函数的返回值
这篇文章就到这里了,如果觉得写的还不错的话还请点个赞,如果有写的不对的地方还请批评指正
更多推荐




所有评论(0)