作为一名C++开发者,我经常被问到如何真正理解STL容器的底层实现。今天我们就从最基础的stack和queue开始,手把手带你从零实现这两个容器的所有关键操作。这不仅仅是语法练习,更是理解STL设计思想的绝佳途径。
stack和queue作为最常用的顺序容器适配器,在实际开发中无处不在。但很多人只是停留在调API的层面,对它们的底层实现原理一知半解。通过这次实现,你将彻底掌握:
STL中的stack和queue并不是独立的容器,而是基于其他容器实现的适配器。这种设计有三大优势:
cpp复制template <class T, class Container = deque<T>>
class stack {
//...
};
template <class T, class Container = deque<T>>
class queue {
//...
};
注意:默认使用deque作为底层容器是经过精心考量的。deque结合了vector和list的优点,既支持随机访问又能在两端高效插入删除。
虽然默认使用deque,但STL允许我们指定其他底层容器:
cpp复制stack<int, vector<int>> s; // 使用vector作为底层
queue<string, list<string>> q; // 使用list作为底层
选择依据:
cpp复制template <class T, class Container = deque<T>>
class stack {
public:
// 类型定义
using value_type = typename Container::value_type;
using size_type = typename Container::size_type;
using reference = typename Container::reference;
using const_reference = typename Container::const_reference;
protected:
Container c; // 底层容器
public:
// 构造函数
explicit stack(const Container& cont = Container()) : c(cont) {}
// 容量操作
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
// 元素访问
reference top() {
if (empty())
throw std::out_of_range("stack<>::top(): empty stack");
return c.back();
}
// 修改操作
void push(const value_type& x) {
c.push_back(x);
}
void pop() {
if (empty())
throw std::out_of_range("stack<>::pop(): empty stack");
c.pop_back();
}
};
关键点说明:
cpp复制template <class T, class Container = deque<T>>
class queue {
public:
// 类型定义(与stack类似)
// ...
protected:
Container c;
public:
// 构造函数
explicit queue(const Container& cont = Container()) : c(cont) {}
// 容量操作
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
// 元素访问
reference front() {
if (empty())
throw std::out_of_range("queue<>::front(): empty queue");
return c.front();
}
reference back() {
if (empty())
throw std::out_of_range("queue<>::back(): empty queue");
return c.back();
}
// 修改操作
void push(const value_type& x) { c.push_back(x); }
void pop() {
if (empty())
throw std::out_of_range("queue<>::pop(): empty queue");
c.pop_front();
}
};
与stack的主要区别:
虽然stack和queue不直接提供迭代器,但我们可以通过底层容器间接实现:
cpp复制// 获取stack底层容器的迭代器(危险操作,慎用!)
auto begin = s.c.begin();
auto end = s.c.end();
// queue类似,但要注意修改操作会导致迭代器失效
警告:直接操作底层容器会破坏stack/queue的不变性条件,仅限调试使用!
STL容器都支持自定义空间配置器,我们的实现也需要兼容:
cpp复制template <class T, class Container = deque<T>,
class Alloc = typename Container::allocator_type>
class stack {
// 使用底层容器的空间配置器
using allocator_type = Alloc;
// ...
};
配置器传播规则:
我们实现的每个操作都提供以下异常安全级别:
对于使用vector作为底层容器的stack,可以预先reserve:
cpp复制stack<int, vector<int>> s;
s.c.reserve(1000); // 预分配空间
通过直接操作底层容器实现批量操作:
cpp复制// 批量push
template <class InputIt>
void push_range(InputIt first, InputIt last) {
c.insert(c.end(), first, last);
}
C++11后应支持移动操作:
cpp复制void push(value_type&& x) {
c.push_back(std::move(x));
}
| 操作类型 | stack影响 | queue影响 |
|---|---|---|
| push | 可能失效 | 可能失效 |
| pop | 一定失效 | 一定失效 |
| swap | 全部失效 | 全部失效 |
解决方案:
STL容器本身不是线程安全的,我们需要额外处理:
cpp复制template <typename T>
class ThreadSafeStack {
stack<T> data;
mutable mutex m;
public:
void push(const T& value) {
lock_guard<mutex> lock(m);
data.push(value);
}
// 其他操作类似...
};
对于元素比较需求,可以扩展实现:
cpp复制template <typename T, typename Container, typename Compare>
class PriorityStack {
// 类似priority_queue的实现
};
完整的实现需要配套测试用例:
cpp复制void test_stack() {
stack<int> s;
// 基本功能测试
assert(s.empty());
s.push(42);
assert(s.size() == 1);
assert(s.top() == 42);
// 异常测试
try {
stack<int> es;
es.pop();
assert(false); // 不应该执行到这里
} catch (const std::out_of_range&) {}
// 性能测试
auto start = chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; ++i) {
s.push(i);
}
auto duration = chrono::duration_cast<chrono::milliseconds>(
chrono::high_resolution_clock::now() - start);
cout << "Push performance: " << duration.count() << "ms" << endl;
}
| 特性 | stack | queue | vector | list |
|---|---|---|---|---|
| 访问模式 | LIFO | FIFO | 随机访问 | 顺序访问 |
| 插入位置 | 仅顶端 | 后端插入 | 任意位置 | 任意位置 |
| 删除位置 | 仅顶端 | 前端删除 | 任意位置 | 任意位置 |
| 迭代器支持 | 无 | 无 | 完整支持 | 完整支持 |
现代C++的新特性可以增强我们的实现:
cpp复制// C++17的nodiscard属性
[[nodiscard]] bool empty() const noexcept;
// C++20的概念约束
template <typename T, typename Container = deque<T>>
requires SequenceContainer<Container>
class stack;
实现这样的底层容器不仅加深了对STL的理解,更能培养我们设计通用库组件的能力。当你在实际项目中遇到需要特定行为的容器时,完全可以基于这种模式进行定制扩展。