不能直接继承std::vector,因其析构函数非虚,多态删除会导致未定义行为;且标准容器未设计为基类,无虚函数、保护接口或扩展点,应采用组合方式封装并手动实现关键接口。

为什么不能直接继承 std::vector 来“实现”它
很多人第一反应是写个类继承 std::vector,加点功能——这不行。std::vector 的析构函数不是 virtual,继承它会导致多态析构未定义行为;而且标准库容器设计上就不鼓励继承,它们没有保护成员、不提供扩展接口。你真正要做的,是手动模拟其核心机制:动态数组 + 容量管理 + 异常安全的元素移动。
push_back 触发扩容时怎么避免重复构造/析构
关键在两步:先分配新内存,再逐个 std::construct_at(C++20)或用 placement new 构造;旧元素用 std::destroy(C++17)或显式调用析构函数清理,最后 operator delete 释放旧内存。别用 memcpy 或 std::copy 直接搬原始字节——对非 trivial 类型会跳过构造/析构,导致资源泄漏或双重析构。
实操建议:
- 扩容倍数选 1.5(如
new_cap = size * 3 / 2),比 2 倍更省内存,且仍保证摊还 O(1) - 用
std::allocator<t></t>管理内存,而不是裸new T[n],否则无法支持自定义分配器 - 移动插入(
push_back(T&&))优先调用移动构造,但需先检查std::is_nothrow_move_constructible_v<t></t>再决定是否能异常安全地移动而非拷贝
迭代器失效规则必须和 std::vector 一致
所有指向被删除元素及之后的迭代器立即失效;push_back 可能导致所有迭代器失效(扩容时)。这不是可选项——这是接口契约。如果你返回的 iterator 是裸指针(比如 T*),那它天然满足该规则;但如果封装成类,必须确保 operator++、operator!= 等行为与原生指针一致,且不能偷偷缓存容量或大小。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:
- 在
for (auto it = v.begin(); it != v.end(); ++it)循环里调v.push_back(),然后继续用it—— 这段代码在你的 vector 里必须崩溃或产生未定义行为,和标准版表现一致 - 实现
erase(iterator)后没返回下一个有效迭代器(应返回it + 1或等价指针),导致v.erase(it++)逻辑错乱
移动语义和异常安全性怎么一起处理
移动构造函数不能抛异常(noexcept),否则 std::vector 在扩容时不敢用移动,只能降级为拷贝。但移动操作本身可能抛异常(比如移动过程中分配器抛 std::bad_alloc)。解决方案是:移动构造只接管指针和大小,不碰元素——把实际元素的移动推迟到后续操作(如 operator= 或 reserve)中,并用 try-catch 保护。
实操要点:
- 移动构造函数体内只做三件事:
data_ = other.data_、size_ = other.size_、capacity_ = other.capacity_,然后置空other的三者 - 移动赋值(
operator=)才真正执行元素移动,并声明为noexcept(false);若移动过程抛异常,必须保证左侧对象仍处于有效状态(强异常保证) - 用
std::is_nothrow_move_assignable_v<t></t>分支判断能否无异常移动,否则回退到拷贝+清空旧数据
最易被忽略的是:哪怕你只支持 int 这种 trivial 类型,也要预留好模板参数约束和分配器接口,否则某天用户传入 std::string 就会暴露移动路径里的析构遗漏问题。


















