std::vector::push_back的异常安全等级取决于元素类型T的移动构造函数是否为noexcept;若T满足is_nothrow_move_constructible_v,则强异常安全,否则仅基本安全。

std::vector::push_back 的异常安全等级取决于什么?
它不总是强异常安全。当 push_back 触发重新分配(即当前容量不足),且元素类型 T 的移动构造函数可能抛出异常时,std::vector 无法保证原状态完全不变——新内存中部分元素可能已构造成功,但旧内存尚未释放,此时若移动构造中途失败,容器会处于“已部分转移”的中间态,只能提供基本异常安全保证。
只有当 T 满足 is_nothrow_move_constructible_v(或退化为拷贝构造且该拷贝也不抛异常)时,push_back 才具备强异常安全保证。
- 检查方式:
static_assert(std::is_nothrow_move_constructible_v
); - 常见陷阱:自定义类未将移动构造函数标记为
noexcept,即使逻辑上不抛异常,标准库仍按可能抛出来处理 - 替代方案:提前调用
reserve()避免重分配,此时push_back仅需就地构造,只要T的构造函数不抛,就满足强保证
std::map::insert 为什么通常有强异常安全保证?
std::map 是基于红黑树实现的节点式容器,插入操作本身不涉及大块内存重分配;每个新节点独立分配,且插入失败(如键已存在)时,新节点直接被销毁,不会影响已有结构。
只要节点分配器(默认 std::allocator)的 allocate 不抛异常(C++17 起 allocate 是 noexcept),且 value_type 的构造函数不抛,整个 insert 就是强异常安全的。
立即学习“C++免费学习笔记(深入)”;
- 例外情况:使用自定义分配器且其
allocate可能抛异常 → 降级为基本保证 -
insert的返回值(pair)在异常发生时仍有效,可安全访问 - 对比
emplace:语义相同,但若传入的参数构造value_type时抛异常,则异常发生在插入前,容器状态完全不变
哪些容器操作只提供基本异常安全保证?
基本保证意味着:异常发生后,容器仍处于有效、可析构的状态,所有对象保持不变(invariant preserved),但可能丢失部分未完成的操作效果(比如部分元素被移除、部分插入失败),且迭代器/引用可能失效。
-
std::vector::resize(n):当n > size()且新增元素的默认构造函数抛异常 → 已构造的元素保留,size()变为实际成功构造的数量(不是原值,也不是目标值) -
std::deque::push_front:内部多段缓冲区管理复杂,某些实现下扩容失败可能导致已分配的段未清理干净,仅保证容器可安全析构 -
std::list::splice(跨容器):若目标 list 分配器不兼容,可能需要逐个节点移动并重新分配,任一节点移动失败即终止,源和目标 list 状态都有效但内容不确定
如何判断你正在使用的操作是否强异常安全?
不能依赖直觉,必须查标准或实现行为。C++ 标准对每种容器的每个成员函数明确规定了异常安全等级,关键看两点:
- 操作是否涉及内存重分配(
vector/string扩容、unordered_map重建桶) -
T或value_type的相关操作(构造、移动、赋值)是否为noexcept
例如:std::string::append 在 C++11 中是强异常安全的,前提是字符类型(通常是 char)的构造不抛;但若使用自定义字符类型且其构造函数抛异常,就只保证基本安全。
真正容易被忽略的是分配器行为——哪怕所有类型操作都 noexcept,只要分配器的 allocate 或 deallocate 可能抛(比如调试版分配器带检查),整个操作就可能降级。生产环境默认分配器虽不抛,但自定义时务必确认 std::allocator_traits::allocate 是否 noexcept。











