在C++标准模板库(STL)中,vector是最常用的动态数组容器之一。作为一名长期使用C++进行开发的工程师,我经常需要向团队成员解释vector的内部机制。理解vector的底层实现不仅能帮助我们更高效地使用它,还能在性能优化时做出更明智的决策。
vector的核心设计理念是"动态扩容的连续数组"。与普通数组不同,vector能够自动管理内存,在元素数量超过当前容量时自动扩容。这种设计带来了几个关键特性:
让我们先看一个最基本的vector类框架:
cpp复制template<class T>
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
vector() : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) {}
~vector() {
if (_start) {
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
private:
iterator _start; // 指向数组首元素
iterator _finish; // 指向最后一个元素的下一个位置
iterator _endofstorage; // 指向分配内存的末尾
};
这三个指针成员构成了vector的核心:
_start:永远指向动态数组的首地址_finish:指向当前已使用空间的末尾(即最后一个元素的下一个位置)_endofstorage:指向整个分配空间的末尾这种三指针设计是vector高效管理内存的关键。size()和capacity()的实现就体现了这一点:
cpp复制size_t size() const { return _finish - _start; }
size_t capacity() const { return _endofstorage - _start; }
提示:理解这三个指针的关系对掌握vector至关重要。可以把它们想象成书架的标记:
_start是书架起点,_finish是最后一本书的位置,_endofstorage是书架实际结束的位置。
vector提供了多种构造函数来满足不同场景的需求。让我们逐一分析每种构造方式的实现原理。
最简单的构造方式,创建一个空vector:
cpp复制vector() : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) {}
这种构造方式不分配任何内存,三个指针都设为nullptr。这是最高效的构造方式,适合后续通过push_back或reserve来逐步构建vector。
拷贝构造需要深拷贝,避免两个vector共享同一块内存:
cpp复制vector(const vector<T>& v)
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
这里使用了"拷贝-交换"惯用法(copy-and-swap idiom),这是一种异常安全的实现方式。具体步骤:
构造包含n个相同元素的vector:
cpp复制vector(size_t n, const T& val = T())
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; ++i) {
push_back(val);
}
}
这里有几个关键点:
reserve一次性分配足够空间,避免多次扩容push_back逐个插入元素T()会调用类型T的默认构造函数通过迭代器范围构造vector,这是最通用的构造方式:
cpp复制template <class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
while (first != last) {
push_back(*first);
++first;
}
}
这个构造函数是模板函数,可以接受任何输入迭代器(包括原生指针)。它逐个复制范围内的元素,保持了原始顺序。
注意:在实际工程中,我们通常会先计算范围大小,然后reserve足够空间,避免多次扩容。但标准库实现为了保持最大通用性,通常不这么做。
vector的迭代器本质就是原生指针的封装,这使得它的操作非常高效。让我们看看迭代器相关接口的实现:
cpp复制iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
反向迭代器的实现稍微复杂一些,通常需要借助reverse_iterator适配器:
cpp复制typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
迭代器的这种简单实现带来了几个重要特性:
+, -, []等操作)经验分享:在调试vector相关问题时,经常需要检查迭代器的有效性。记住,vector的迭代器在扩容后会失效,因为内存地址可能改变。
vector提供了一系列容量相关操作,让我们先看基础接口的实现:
cpp复制bool empty() const { return _start == _finish; }
size_t size() const { return _finish - _start; }
size_t capacity() const { return _endofstorage - _start; }
这些函数都非常简单直接,利用了指针算术的特性。值得注意的是,empty()不应该用size() == 0来判断,因为直接比较指针通常更高效。
reserve是vector性能优化的关键函数,它允许我们预先分配足够的内存:
cpp复制void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* new_start = new T[n];
// 拷贝原有元素
for(size_t i = 0; i < old_size; ++i) {
new_start[i] = _start[i];
}
delete[] _start;
_start = new_start;
_finish = _start + old_size;
_endofstorage = _start + n;
}
}
实现要点:
vector的自动扩容通常发生在push_back时:
cpp复制void push_back(const T& val) {
if (_finish == _endofstorage) {
size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(new_capacity);
}
*_finish = val;
++_finish;
}
这里展示了典型的vector扩容策略:初始容量为0,第一次扩容到4,之后每次翻倍。这种指数增长策略保证了插入操作的平摊时间复杂度为O(1)。
避坑指南:在已知元素数量的情况下,应该先调用reserve预分配空间,避免多次扩容带来的性能损失和内存碎片。
resize改变了vector中元素的数量,有两种行为模式:
cpp复制void resize(size_t n, const T& val = T()) {
if (n < size()) {
_finish = _start + n;
} else {
if (n > capacity()) {
reserve(n);
}
for (iterator it = _finish; it != _start + n; ++it) {
*it = val;
}
_finish = _start + n;
}
}
关键行为:
_finish指针(元素未被销毁,只是不可见)注意:resize可能会调用元素的构造函数/赋值运算符,这对某些复杂类型可能有性能影响。
vector提供了多种元素访问方式,最常用的是operator[]和at():
cpp复制T& operator[](size_t pos) {
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const {
assert(pos < size());
return _start[pos];
}
T& at(size_t pos) {
if (pos >= size()) {
throw std::out_of_range("vector::at");
}
return _start[pos];
}
两者的主要区别:
operator[]使用断言检查,在release模式下可能不检查at()会抛出异常,适合需要安全检查的场景性能提示:在确定索引有效的情况下,使用
operator[]更高效,因为它没有异常处理开销。
尾部操作是vector最高效的修改方式:
cpp复制void push_back(const T& val) {
if (_finish == _endofstorage) {
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
++_finish;
}
void pop_back() {
assert(!empty());
--_finish;
// 注意:这里没有销毁对象,只是调整指针
}
push_back需要考虑扩容,而pop_back只需调整指针。注意标准库实现可能会在pop_back时调用元素的析构函数。
中间位置的插入和删除效率较低,因为需要移动元素:
cpp复制iterator insert(iterator pos, const T& val) {
assert(pos >= begin() && pos <= end());
if (_finish == _endofstorage) {
size_t offset = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + offset; // 扩容后迭代器失效,需要重新计算
}
iterator it = _finish;
while (it != pos) {
*it = *(it - 1);
--it;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos) {
assert(pos >= begin() && pos < end());
iterator it = pos;
while (it + 1 != _finish) {
*it = *(it + 1);
++it;
}
--_finish;
return pos;
}
实现要点:
工程经验:在需要频繁中间插入/删除的场景下,考虑使用list或deque可能更合适。
vector的某些操作会使迭代器失效,这是实际开发中最容易出错的地方之一。主要失效场景包括:
capacity()改变的操作(push_back、insert、reserve等)都会使所有迭代器失效cpp复制// 错误示例:遍历时删除元素
for (auto it = vec.begin(); it != vec.end(); ) {
if (condition(*it)) {
it = vec.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
正确做法是使用erase返回的新迭代器,或者使用remove-erase惯用法:
cpp复制vec.erase(std::remove_if(vec.begin(), vec.end(), condition), vec.end());
根据多年工程经验,总结出以下vector优化建议:
预分配空间:在知道大致元素数量时,先调用reserve
cpp复制vector<int> vec;
vec.reserve(1000); // 避免多次扩容
使用emplace_back代替push_back:避免不必要的拷贝/移动
cpp复制vec.emplace_back(args...); // 直接在容器内构造对象
减少中间插入:如果需要频繁中间插入,考虑其他容器
批量操作:使用范围插入比单元素插入高效
cpp复制vec.insert(vec.end(), another_vec.begin(), another_vec.end());
移动语义:对于临时对象,使用std::move减少拷贝
cpp复制string large_str = get_large_string();
vec.push_back(std::move(large_str));
vector的实现需要考虑异常安全保证。主要关注点:
push_back)要么完全成功,要么保持原状swap)保证不抛出异常例如,在reserve实现中,我们应先分配新内存并拷贝成功后再释放旧内存:
cpp复制void reserve(size_t n) {
if (n > capacity()) {
T* new_start = new T[n]; // 可能抛出bad_alloc
try {
// 拷贝构造可能抛出异常
for(size_t i = 0; i < size(); ++i) {
new (new_start + i) T(_start[i]); // placement new
}
} catch (...) {
delete[] new_start;
throw;
}
// 只有上面都成功了才继续
for(size_t i = 0; i < size(); ++i) {
_start[i].~T(); // 显式析构
}
delete[] _start;
_start = new_start;
_finish = _start + size();
_endofstorage = _start + n;
}
}
这种实现提供了强异常安全保证:如果任何步骤失败,原始vector保持不变。
综合前面的讨论,下面是一个相对完整的vector实现框架:
cpp复制template<class T>
class vector {
public:
// 类型定义
typedef T* iterator;
typedef const T* const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
// 构造/析构
vector() : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) {}
vector(size_t n, const T& val = T())
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; ++i) {
push_back(val);
}
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
while (first != last) {
push_back(*first);
++first;
}
}
vector(const vector<T>& v)
: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
~vector() {
clear();
delete[] _start;
}
// 容量相关
size_t size() const { return _finish - _start; }
size_t capacity() const { return _endofstorage - _start; }
bool empty() const { return _start == _finish; }
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* new_start = new T[n];
for(size_t i = 0; i < old_size; ++i) {
new_start[i] = _start[i];
}
delete[] _start;
_start = new_start;
_finish = _start + old_size;
_endofstorage = _start + n;
}
}
void resize(size_t n, const T& val = T()) {
if (n < size()) {
_finish = _start + n;
} else {
if (n > capacity()) {
reserve(n);
}
for (iterator it = _finish; it != _start + n; ++it) {
*it = val;
}
_finish = _start + n;
}
}
// 元素访问
T& operator[](size_t pos) { return _start[pos]; }
const T& operator[](size_t pos) const { return _start[pos]; }
T& front() { return *_start; }
const T& front() const { return *_start; }
T& back() { return *(_finish - 1); }
const T& back() const { return *(_finish - 1); }
// 修改操作
void push_back(const T& val) {
if (_finish == _endofstorage) {
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
++_finish;
}
void pop_back() {
assert(!empty());
--_finish;
}
iterator insert(iterator pos, const T& val) {
assert(pos >= begin() && pos <= end());
if (_finish == _endofstorage) {
size_t offset = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + offset;
}
iterator it = _finish;
while (it != pos) {
*it = *(it - 1);
--it;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos) {
assert(pos >= begin() && pos < end());
iterator it = pos;
while (it + 1 != _finish) {
*it = *(it + 1);
++it;
}
--_finish;
return pos;
}
void clear() {
_finish = _start;
}
void swap(vector<T>& other) {
std::swap(_start, other._start);
std::swap(_finish, other._finish);
std::swap(_endofstorage, other._endofstorage);
}
// 迭代器
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
为了验证我们的vector实现,可以编写以下测试代码:
cpp复制void test_vector() {
// 基础功能测试
vector<int> v1;
assert(v1.empty());
assert(v1.size() == 0);
// 插入元素测试
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
assert(v1.size() == 3);
assert(v1[0] == 1 && v1[1] == 2 && v1[2] == 3);
// 扩容测试
v1.reserve(100);
assert(v1.capacity() >= 100);
assert(v1.size() == 3);
// 拷贝构造测试
vector<int> v2(v1);
assert(v2.size() == 3);
assert(v2[0] == 1 && v2[1] == 2 && v2[2] == 3);
// 插入删除测试
v2.insert(v2.begin() + 1, 10);
assert(v2.size() == 4);
assert(v2[1] == 10);
v2.erase(v2.begin() + 1);
assert(v2.size() == 3);
assert(v2[1] == 2);
// 边界条件测试
vector<int> v3(10, 5);
assert(v3.size() == 10);
for (auto num : v3) {
assert(num == 5);
}
v3.resize(5);
assert(v3.size() == 5);
v3.resize(15, 10);
assert(v3.size() == 15);
for (size_t i = 0; i < 5; ++i) {
assert(v3[i] == 5);
}
for (size_t i = 5; i < 15; ++i) {
assert(v3[i] == 10);
}
std::cout << "All vector tests passed!" << std::endl;
}
这些测试覆盖了vector的主要功能点,包括构造、插入、删除、扩容、拷贝等操作。在实际工程中,我们还需要考虑更复杂的测试场景,特别是涉及异常安全和性能的测试。
我们的简化实现与标准库vector有一些重要区别,理解这些区别有助于更好地使用标准库:
例如,标准库的push_back实现可能更像这样:
cpp复制void push_back(const T& val) {
if (_finish == _endofstorage) {
emplace_back_realloc(val);
} else {
allocator_traits::construct(get_allocator(), _finish, val);
++_finish;
}
}
template<class... Args>
void emplace_back(Args&&... args) {
if (_finish == _endofstorage) {
emplace_back_realloc(std::forward<Args>(args)...);
} else {
allocator_traits::construct(get_allocator(), _finish, std::forward<Args>(args)...);
++_finish;
}
}
这种实现更复杂,但提供了更好的异常安全和更高效的emplace操作。
在实际项目中,除非有特殊需求,否则应该优先使用标准库vector。自制vector主要用于学习目的或特殊场景下的定制需求。