应使用模板实现通用栈,如template<typename T> class SimpleStack,以避免重复造轮子;核心是类型参数化,用std::vector存储,提供const/non-const top()重载,pop()不返回值以保证异常安全,优先考虑缓存友好的vector而非list,并注意T需满足可拷贝/可移动、析构不抛异常等隐式要求。

用 template 实现通用栈,别写多个重复类
直接用 template 是唯一合理做法。手写 int_stack、string_stack 等一堆类,维护成本高、易出错,还违背 C++ 类型抽象原则。
标准库的 std::stack 就是模板实现,自己造轮子时照着逻辑抄就行,核心是把类型参数化:
template <typename T>
class SimpleStack {
private:
std::vector<T> data_;
public:
void push(const T& x) { data_.push_back(x); }
void pop() { if (!empty()) data_.pop_back(); }
T& top() { return data_.back(); }
const T& top() const { return data_.back(); }
bool empty() const { return data_.empty(); }
size_t size() const { return data_.size(); }
};
注意:top() 必须提供 const 和非 const 两个重载,否则 const SimpleStack<int> 调用 top() 会编译失败。
pop() 不返回值,这是故意设计的
很多初学者想让 pop() 返回被弹出的元素,比如 T pop() —— 这会导致异常安全问题:如果 T 的拷贝构造抛异常,栈已修改(pop_back() 已执行),但返回失败,状态不一致。
立即学习“C++免费学习笔记(深入)”;
标准做法(也是你该模仿的)是分两步:top() 查看,再 pop() 移除。这样无论哪一步出异常,栈状态都可预测。
-
pop()只负责修改内部状态,不涉及对象构造 - 如果真需要“取并删”,封装一个
T take_top()方法,但内部必须先top()再pop(),且文档注明它可能抛异常 - 对
move-only类型(如std::unique_ptr),top()返回引用,take_top()应返回T(触发移动)
内存布局和性能:vector 比 list 更合适
有人觉得“栈是后进先出,应该用链表”,其实不然。现代 CPU 缓存友好性远比理论上的 O(1) 指针操作重要。
std::vector 在 push_back/ pop_back 上平均 O(1),局部性好;而 std::list 每次分配新节点,内存分散,cache miss 高。
- 除非你明确需要
push/pop严格 O(1) 且不接受偶尔的 realloc(比如实时系统),否则别换容器 - 如果担心扩容开销,可在构造时用
reserve()预留空间,例如SimpleStack<int> s; s.reserve(1024); - 不要在栈里存大对象(如
std::string或自定义结构体)而不考虑移动语义——确保T支持移动,或传入右值引用
类型约束没加没关系,但得知道什么时候会崩
上面的模板没加 static_assert 或 requires(C++20),看起来啥都能塞进去。实际中,只要 T 满足这几个隐式要求就能用:
- 可默认构造(
std::vector构造时可能需要) - 可拷贝或可移动(
push()传入 const ref,内部 vector 存储时需拷贝/移动) - 析构函数不能抛异常(否则
pop()可能中途崩溃)
典型崩坏场景:SimpleStack<std::array<char, 1024*1024>> —— 每次 push 都拷贝 1MB,慢到卡死;或者存一个析构会 throw 的类,pop() 直接 terminate。
真正需要强约束时,C++20 可加 requires std::copyable<T>,但多数业务场景下,靠文档和测试覆盖更实际。


















