作为C++开发者,我们每天都在使用STL中的vector容器,但你真的了解它的内部工作原理吗?当我第一次尝试自己实现vector时,才发现原来这个看似简单的动态数组背后隐藏着这么多精妙的设计。
vector之所以成为C++中最常用的容器,主要因为它结合了数组的高效随机访问和动态扩容的灵活性。标准库中的vector经过多年优化,性能已经非常出色,但理解其底层实现原理对于提升我们的编程能力至关重要。
任何vector实现都需要三个核心成员变量:
cpp复制template <typename T>
class vector {
private:
T* _data; // 指向动态分配的数组
size_t _size; // 当前存储的元素数量
size_t _capacity; // 当前分配的容量
};
这里有几个关键点需要注意:
_data使用裸指针而不是智能指针,这是为了与标准库实现保持一致_size和_capacity使用size_t类型,确保能表示足够大的值vector的核心在于其内存管理策略。与静态数组不同,vector会根据需要自动扩容,但扩容操作是有代价的。每次扩容都需要:
这种操作的时间复杂度是O(n),因此频繁扩容会严重影响性能。这就是为什么vector通常采用指数级扩容策略(如每次扩容为当前容量的2倍),这样可以将均摊时间复杂度降到O(1)。
最简单的构造函数创建一个空vector:
cpp复制vector() : _data(nullptr), _size(0), _capacity(0) {}
这个实现非常直接,但要注意:
这个构造函数创建包含n个元素的vector,每个元素初始化为val:
cpp复制explicit vector(size_t n, const T& val = T()) {
_data = new T[n];
for (size_t i = 0; i < n; ++i) {
_data[i] = val; // 使用赋值操作而非placement new
}
_size = _capacity = n;
}
几个注意事项:
explicit防止隐式转换拷贝构造需要实现深拷贝:
cpp复制vector(const vector& other) {
_data = new T[other._capacity];
for (size_t i = 0; i < other._size; ++i) {
_data[i] = other._data[i]; // 调用元素的拷贝赋值运算符
}
_size = other._size;
_capacity = other._capacity;
}
深拷贝确保了两个vector完全独立,修改一个不会影响另一个。这是容器类的基本要求。
析构函数负责释放分配的内存:
cpp复制~vector() {
delete[] _data;
_data = nullptr;
_size = _capacity = 0;
}
注意点:
push_back是vector最常用的操作之一:
cpp复制void push_back(const T& val) {
if (_size == _capacity) {
resize_capacity(_capacity == 0 ? 1 : _capacity * 2);
}
_data[_size++] = val;
}
关键点:
扩容是vector性能的关键:
cpp复制void resize_capacity(size_t new_cap) {
if (new_cap <= _capacity) return;
T* new_data = new T[new_cap];
for (size_t i = 0; i < _size; ++i) {
new_data[i] = _data[i]; // 元素拷贝
}
delete[] _data;
_data = new_data;
_capacity = new_cap;
}
注意事项:
提供类似数组的访问方式:
cpp复制T& operator[](size_t pos) {
return _data[pos];
}
const T& operator[](size_t pos) const {
return _data[pos];
}
这里实现了const和非const两个版本,以支持对const vector的访问。注意这个实现没有边界检查,与标准库行为一致。
最简单的迭代器就是原生指针:
cpp复制using iterator = T*;
using const_iterator = const T*;
cpp复制iterator begin() { return _data; }
iterator end() { return _data + _size; }
const_iterator begin() const { return _data; }
const_iterator end() const { return _data + _size; }
这使得我们的vector可以用于基于范围的for循环:
cpp复制for (auto& item : vec) {
// 处理item
}
cpp复制#include <iostream>
int main() {
vector<int> vec;
for (int i = 0; i < 10; ++i) {
vec.push_back(i * 2);
}
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
// 输出:0 2 4 6 8 10 12 14 16 18
return 0;
}
可以测试不同扩容策略的性能差异:
cpp复制void test_performance() {
const size_t N = 1000000;
// 测试我们的vector
{
vector<int> vec;
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < N; ++i) {
vec.push_back(i);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Custom vector: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
}
// 测试std::vector
{
std::vector<int> vec;
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < N; ++i) {
vec.push_back(i);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "std::vector: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms\n";
}
}
C++11引入了移动语义,可以显著提高性能:
cpp复制// 移动构造函数
vector(vector&& other) noexcept
: _data(other._data), _size(other._size), _capacity(other._capacity) {
other._data = nullptr;
other._size = other._capacity = 0;
}
// 移动赋值运算符
vector& operator=(vector&& other) noexcept {
if (this != &other) {
delete[] _data;
_data = other._data;
_size = other._size;
_capacity = other._capacity;
other._data = nullptr;
other._size = other._capacity = 0;
}
return *this;
}
emplace_back可以直接在容器内构造元素,避免临时对象的创建和拷贝:
cpp复制template <typename... Args>
void emplace_back(Args&&... args) {
if (_size == _capacity) {
resize_capacity(_capacity == 0 ? 1 : _capacity * 2);
}
new (_data + _size) T(std::forward<Args>(args)...);
++_size;
}
当前实现缺乏异常安全保证。更健壮的实现应该:
在实现vector时最容易犯的错误就是内存管理问题:
经过多次实践,我发现以下优化技巧很有效:
调试自定义容器时特别有用的一些方法: