在C++标准模板库(STL)中,stack和queue作为两种基础容器适配器,它们的实现方式体现了经典的数据结构封装思想。与直接使用底层容器不同,这两种适配器通过限制操作接口来强制遵循特定的数据访问规则。
stack遵循LIFO(后进先出)原则,就像餐厅里叠放的餐盘,你只能从最顶部取放。queue则遵循FIFO(先进先出)原则,类似超市排队结账,最早排队的人最先离开。这种特性使得它们在算法实现中具有不可替代性。
关键认知:stack和queue都是容器适配器,这意味着它们是在现有序列容器(如deque或list)基础上构建的接口封装,而非独立实现的数据存储结构。
标准库中stack默认使用deque作为底层容器,这是综合考虑了内存效率与操作性能的结果。我们的实现同样采用模板化设计:
cpp复制template<typename T, typename Container = std::deque<T>>
class Stack {
private:
Container _container; // 底层容器
};
选择deque而非vector的原因在于:
push操作的边界处理值得注意:
cpp复制void push(const T& value) {
// 异常安全保证:先构造临时对象再插入
_container.push_back(value);
}
void push(T&& value) {
_container.push_back(std::move(value)); // 移动语义优化
}
pop操作需要确保栈非空:
cpp复制void pop() {
if(empty())
throw std::out_of_range("Stack is empty");
_container.pop_back();
}
top操作的常量版本重载:
cpp复制T& top() {
return _container.back();
}
const T& top() const {
return _container.back();
}
queue需要同时支持前端删除和后端插入,因此list通常比deque更适合作为底层容器:
cpp复制template<typename T, typename Container = std::list<T>>
class Queue {
private:
Container _container;
};
选择list的优势:
enqueue操作的完美转发实现:
cpp复制template<typename U>
void enqueue(U&& value) {
_container.push_back(std::forward<U>(value));
}
dequeue操作的异常安全版本:
cpp复制T dequeue() {
if(empty())
throw std::out_of_range("Queue is empty");
T value = std::move(_container.front());
_container.pop_front();
return value;
}
peek操作的多版本支持:
cpp复制T& front() { return _container.front(); }
const T& front() const { return _container.front(); }
T& back() { return _container.back(); }
const T& back() const { return _container.back(); }
生产级队列通常需要考虑线程安全:
cpp复制template<typename T>
class ConcurrentQueue {
std::queue<T> _queue;
std::mutex _mutex;
std::condition_variable _cond;
public:
void enqueue(T item) {
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(std::move(item));
_cond.notify_one();
}
bool dequeue(T& item) {
std::unique_lock<std::mutex> lock(_mutex);
_cond.wait(lock, [this]{ return !_queue.empty(); });
item = std::move(_queue.front());
_queue.pop();
return true;
}
};
测试不同容器作为底层存储时的性能差异:
| 操作类型 | deque耗时(ns) | list耗时(ns) | vector耗时(ns) |
|---|---|---|---|
| push | 125 | 138 | 117 |
| pop | 98 | 105 | 210* |
| 遍历 | 356 | 420 | 310 |
*vector在pop_front时需要移动所有元素,性能最差
固定大小栈的实现:
cpp复制template<typename T, size_t Capacity>
class FixedStack {
std::array<T, Capacity> _data;
size_t _size = 0;
public:
void push(const T& value) {
if(_size >= Capacity)
throw std::overflow_error("Stack full");
_data[_size++] = value;
}
// ...其他接口
};
优先队列的底层堆实现:
cpp复制template<typename T>
class PriorityQueue {
std::vector<T> _heap;
void heapify_up(size_t index) {
while(index > 0) {
size_t parent = (index-1)/2;
if(_heap[index] < _heap[parent]) break;
std::swap(_heap[index], _heap[parent]);
index = parent;
}
}
// ...其他操作
};
栈/队列实现中常见的内存错误:
诊断工具推荐:
使用perf工具分析操作耗时:
bash复制perf record ./stack_test
perf report
典型性能优化方向:
使用ThreadSanitizer检测竞争条件:
bash复制g++ -fsanitize=thread -pie test_queue.cpp
验证方法:
C++20引入concept后可以更好地约束容器类型:
cpp复制template<typename T, typename Container>
requires SequenceContainer<Container> &&
requires(Container c, T v) {
{ c.push_back(v) } -> std::same_as<void>;
{ c.pop_back() } -> std::same_as<void>;
}
class Stack { /*...*/ };
根据操作的关键程度实现不同级别的异常安全:
完美转发在泛型编程中的应用:
cpp复制template<typename... Args>
void emplace(Args&&... args) {
_container.emplace_back(std::forward<Args>(args)...);
}
这种实现方式比push+临时对象构造效率更高,特别是对于复杂对象。