C++ STL之异常安全与noexcept详解
一、C++ 异常层次结构
C++ 标准库的异常体系以 std::exception 为基类,派生两大分支:
#mermaid-svg-QrGYaY0wGlHXJ441{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QrGYaY0wGlHXJ441 .error-icon{fill:#552222;}#mermaid-svg-QrGYaY0wGlHXJ441 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QrGYaY0wGlHXJ441 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QrGYaY0wGlHXJ441 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QrGYaY0wGlHXJ441 .marker.cross{stroke:#333333;}#mermaid-svg-QrGYaY0wGlHXJ441 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QrGYaY0wGlHXJ441 p{margin:0;}#mermaid-svg-QrGYaY0wGlHXJ441 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster-label text{fill:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster-label span{color:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster-label span p{background-color:transparent;}#mermaid-svg-QrGYaY0wGlHXJ441 .label text,#mermaid-svg-QrGYaY0wGlHXJ441 span{fill:#333;color:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 .node rect,#mermaid-svg-QrGYaY0wGlHXJ441 .node circle,#mermaid-svg-QrGYaY0wGlHXJ441 .node ellipse,#mermaid-svg-QrGYaY0wGlHXJ441 .node polygon,#mermaid-svg-QrGYaY0wGlHXJ441 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QrGYaY0wGlHXJ441 .rough-node .label text,#mermaid-svg-QrGYaY0wGlHXJ441 .node .label text,#mermaid-svg-QrGYaY0wGlHXJ441 .image-shape .label,#mermaid-svg-QrGYaY0wGlHXJ441 .icon-shape .label{text-anchor:middle;}#mermaid-svg-QrGYaY0wGlHXJ441 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QrGYaY0wGlHXJ441 .rough-node .label,#mermaid-svg-QrGYaY0wGlHXJ441 .node .label,#mermaid-svg-QrGYaY0wGlHXJ441 .image-shape .label,#mermaid-svg-QrGYaY0wGlHXJ441 .icon-shape .label{text-align:center;}#mermaid-svg-QrGYaY0wGlHXJ441 .node.clickable{cursor:pointer;}#mermaid-svg-QrGYaY0wGlHXJ441 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QrGYaY0wGlHXJ441 .arrowheadPath{fill:#333333;}#mermaid-svg-QrGYaY0wGlHXJ441 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QrGYaY0wGlHXJ441 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QrGYaY0wGlHXJ441 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QrGYaY0wGlHXJ441 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QrGYaY0wGlHXJ441 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QrGYaY0wGlHXJ441 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster text{fill:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 .cluster span{color:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QrGYaY0wGlHXJ441 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QrGYaY0wGlHXJ441 rect.text{fill:none;stroke-width:0;}#mermaid-svg-QrGYaY0wGlHXJ441 .icon-shape,#mermaid-svg-QrGYaY0wGlHXJ441 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QrGYaY0wGlHXJ441 .icon-shape p,#mermaid-svg-QrGYaY0wGlHXJ441 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QrGYaY0wGlHXJ441 .icon-shape .label rect,#mermaid-svg-QrGYaY0wGlHXJ441 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QrGYaY0wGlHXJ441 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QrGYaY0wGlHXJ441 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QrGYaY0wGlHXJ441 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} std::exception
std::logic_error
std::runtime_error
std::system_error
std::invalid_argument
std::out_of_range
std::length_error
std::overflow_error
std::underflow_error
携带 std::error_code
- logic_error:可在编译期或运行前检测的逻辑错误(如越界、非法参数),理论上可通过代码审查避免。
- runtime_error:运行时条件导致的错误(如溢出、I/O 异常),无法通过静态检查杜绝。
- system_error :C++11 引入,包装操作系统错误码(
std::error_code),常见于std::filesystem和<system_error>头文件中的 API。通过.code()获取平台无关的error_code,通过.code().value()获取原生错误码。
cpp
#include <system_error>
#include <iostream>
int main() {
try {
throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory),
"打开配置文件失败");
} catch (const std::system_error& e) {
std::cout << "what(): " << e.what() << "\n";
std::cout << "code: " << e.code().value() << "\n"; // 2 (POSIX ENOENT)
std::cout << "message: " << e.code().message() << "\n"; // "No such file or directory"
}
}
二、noexcept 说明符与运算符
C++11 引入 noexcept 关键字,标识函数不会向外传播异常。编译器可利用此信息优化代码;若 noexcept 函数内部抛异常,程序直接调用 std::terminate,不会发生栈展开。
cpp
void safe_func() noexcept; // 保证不抛异常
void maybe_throw() noexcept(false); // 可能抛异常(默认)
noexcept 也可按条件计算:
cpp
void move_if_possible(T& dest, T&& src) noexcept(std::is_nothrow_move_constructible_v<T>);
noexcept 运算符可在编译期查询表达式是否保证不抛:
cpp
static_assert(noexcept(std::declval<std::vector<int>>().size())); // true
noexcept 对 STL 容器的影响
STL 容器大量使用 SFINAE + noexcept 来决定选用哪个重载。最关键的场景是 push_back / resize 等操作中的元素移动策略。
#mermaid-svg-FbxvnqFfyO2psJOc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FbxvnqFfyO2psJOc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FbxvnqFfyO2psJOc .error-icon{fill:#552222;}#mermaid-svg-FbxvnqFfyO2psJOc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FbxvnqFfyO2psJOc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FbxvnqFfyO2psJOc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FbxvnqFfyO2psJOc .marker.cross{stroke:#333333;}#mermaid-svg-FbxvnqFfyO2psJOc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FbxvnqFfyO2psJOc p{margin:0;}#mermaid-svg-FbxvnqFfyO2psJOc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FbxvnqFfyO2psJOc .cluster-label text{fill:#333;}#mermaid-svg-FbxvnqFfyO2psJOc .cluster-label span{color:#333;}#mermaid-svg-FbxvnqFfyO2psJOc .cluster-label span p{background-color:transparent;}#mermaid-svg-FbxvnqFfyO2psJOc .label text,#mermaid-svg-FbxvnqFfyO2psJOc span{fill:#333;color:#333;}#mermaid-svg-FbxvnqFfyO2psJOc .node rect,#mermaid-svg-FbxvnqFfyO2psJOc .node circle,#mermaid-svg-FbxvnqFfyO2psJOc .node ellipse,#mermaid-svg-FbxvnqFfyO2psJOc .node polygon,#mermaid-svg-FbxvnqFfyO2psJOc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FbxvnqFfyO2psJOc .rough-node .label text,#mermaid-svg-FbxvnqFfyO2psJOc .node .label text,#mermaid-svg-FbxvnqFfyO2psJOc .image-shape .label,#mermaid-svg-FbxvnqFfyO2psJOc .icon-shape .label{text-anchor:middle;}#mermaid-svg-FbxvnqFfyO2psJOc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FbxvnqFfyO2psJOc .rough-node .label,#mermaid-svg-FbxvnqFfyO2psJOc .node .label,#mermaid-svg-FbxvnqFfyO2psJOc .image-shape .label,#mermaid-svg-FbxvnqFfyO2psJOc .icon-shape .label{text-align:center;}#mermaid-svg-FbxvnqFfyO2psJOc .node.clickable{cursor:pointer;}#mermaid-svg-FbxvnqFfyO2psJOc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FbxvnqFfyO2psJOc .arrowheadPath{fill:#333333;}#mermaid-svg-FbxvnqFfyO2psJOc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FbxvnqFfyO2psJOc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FbxvnqFfyO2psJOc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FbxvnqFfyO2psJOc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FbxvnqFfyO2psJOc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FbxvnqFfyO2psJOc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FbxvnqFfyO2psJOc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FbxvnqFfyO2psJOc .cluster text{fill:#333;}#mermaid-svg-FbxvnqFfyO2psJOc .cluster span{color:#333;}#mermaid-svg-FbxvnqFfyO2psJOc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FbxvnqFfyO2psJOc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FbxvnqFfyO2psJOc rect.text{fill:none;stroke-width:0;}#mermaid-svg-FbxvnqFfyO2psJOc .icon-shape,#mermaid-svg-FbxvnqFfyO2psJOc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FbxvnqFfyO2psJOc .icon-shape p,#mermaid-svg-FbxvnqFfyO2psJOc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FbxvnqFfyO2psJOc .icon-shape .label rect,#mermaid-svg-FbxvnqFfyO2psJOc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FbxvnqFfyO2psJOc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FbxvnqFfyO2psJOc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FbxvnqFfyO2psJOc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
vector::push_back
需要扩容
T 的移动构造
是否 noexcept?
移动元素→强安全
O(1) 摊销
拷贝元素→强安全
O(n)
扩容成功
原因:vector 需要在重新分配时保证强异常安全 ------如果移动中途抛出,旧元素已被破坏,无法回滚。复制则不同,旧元素保持不变,复制失败直接丢弃新缓冲区即可。因此标准要求:只有 noexcept 的移动构造才会在 vector 扩容中被选用。
cpp
struct Movable {
Movable(Movable&&) noexcept { /* 安全移动 */ }
};
struct MaybeThrow {
MaybeThrow(MaybeThrow&&) { /* 可能抛异常 */ }
};
std::vector<Movable> a;
a.push_back(Movable{}); // 走移动,O(1) 摊销
std::vector<MaybeThrow> b;
b.push_back(MaybeThrow{}); // 走拷贝,O(n) 性能损失
list 和 forward_list 则不同 ------它们不需要连续内存,插入/删除不涉及元素搬迁。它们的移动构造始终是 noexcept:
cpp
static_assert(noexcept(std::list<int>(std::declval<std::list<int>>()))); // true
这就是为什么 std::list 的移动构造始终 O(1),而 std::vector 的移动只有在元素 noexcept 时才能利用。
三、异常安全三保证
Herb Sutter 在《Exceptional C++》中定义了三个级别的异常安全:
| 级别 | 描述 | 典型实现 |
|---|---|---|
| nothrow | 绝不对外抛异常 | 析构函数、swap、noexcept 标记函数 |
| strong | 操作要么完全成功,要么回滚到操作前状态 | copy-and-swap |
| basic | 操作失败时资源不泄漏,对象处于可用但不确定的状态 | 大多数 STL 容器操作 |
#mermaid-svg-0oYRtewpaBo7SQ2J{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0oYRtewpaBo7SQ2J .error-icon{fill:#552222;}#mermaid-svg-0oYRtewpaBo7SQ2J .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0oYRtewpaBo7SQ2J .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0oYRtewpaBo7SQ2J .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0oYRtewpaBo7SQ2J .marker.cross{stroke:#333333;}#mermaid-svg-0oYRtewpaBo7SQ2J svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0oYRtewpaBo7SQ2J p{margin:0;}#mermaid-svg-0oYRtewpaBo7SQ2J .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster-label text{fill:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster-label span{color:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster-label span p{background-color:transparent;}#mermaid-svg-0oYRtewpaBo7SQ2J .label text,#mermaid-svg-0oYRtewpaBo7SQ2J span{fill:#333;color:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J .node rect,#mermaid-svg-0oYRtewpaBo7SQ2J .node circle,#mermaid-svg-0oYRtewpaBo7SQ2J .node ellipse,#mermaid-svg-0oYRtewpaBo7SQ2J .node polygon,#mermaid-svg-0oYRtewpaBo7SQ2J .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0oYRtewpaBo7SQ2J .rough-node .label text,#mermaid-svg-0oYRtewpaBo7SQ2J .node .label text,#mermaid-svg-0oYRtewpaBo7SQ2J .image-shape .label,#mermaid-svg-0oYRtewpaBo7SQ2J .icon-shape .label{text-anchor:middle;}#mermaid-svg-0oYRtewpaBo7SQ2J .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0oYRtewpaBo7SQ2J .rough-node .label,#mermaid-svg-0oYRtewpaBo7SQ2J .node .label,#mermaid-svg-0oYRtewpaBo7SQ2J .image-shape .label,#mermaid-svg-0oYRtewpaBo7SQ2J .icon-shape .label{text-align:center;}#mermaid-svg-0oYRtewpaBo7SQ2J .node.clickable{cursor:pointer;}#mermaid-svg-0oYRtewpaBo7SQ2J .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0oYRtewpaBo7SQ2J .arrowheadPath{fill:#333333;}#mermaid-svg-0oYRtewpaBo7SQ2J .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0oYRtewpaBo7SQ2J .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0oYRtewpaBo7SQ2J .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0oYRtewpaBo7SQ2J .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0oYRtewpaBo7SQ2J .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0oYRtewpaBo7SQ2J .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster text{fill:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J .cluster span{color:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0oYRtewpaBo7SQ2J .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0oYRtewpaBo7SQ2J rect.text{fill:none;stroke-width:0;}#mermaid-svg-0oYRtewpaBo7SQ2J .icon-shape,#mermaid-svg-0oYRtewpaBo7SQ2J .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0oYRtewpaBo7SQ2J .icon-shape p,#mermaid-svg-0oYRtewpaBo7SQ2J .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0oYRtewpaBo7SQ2J .icon-shape .label rect,#mermaid-svg-0oYRtewpaBo7SQ2J .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0oYRtewpaBo7SQ2J .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0oYRtewpaBo7SQ2J .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0oYRtewpaBo7SQ2J :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 异常安全三保证
nothrow
绝不抛异常
strong
全成功或回滚
basic
不泄漏·有效但不确定
析构函数
swap
noexcept 函数
copy‑and‑swap
vector 扩容(复制)
STL 容器的大多数操作
Strong 保证:copy-and-swap 惯用法
cpp
class Widget {
std::vector<int> data_;
public:
Widget(const Widget& other) : data_(other.data_) {}
void merge(const Widget& other) {
Widget tmp(other); // 1. 复制构造(可能抛)
data_.insert(data_.end(),
tmp.data_.begin(), tmp.data_.end()); // 2. 插入(可能抛)
// 如果以上任一步失败,tmp 析构,原始 data_ 不变
data_.swap(tmp.data_); // 3. swap(nothrow)
}
};
四、RAII 与析构函数不应抛异常
C++ 核心指南(C.35):析构函数、swap、删除操作应当不向外抛异常。
为什么?如果有两个异常同时存在(例如第一个异常正在栈展开时析构函数又抛了异常),程序会立即 std::terminate。
cpp
struct Bad {
~Bad() {
throw std::runtime_error("oops"); // 致命:可能在栈展开时被调用
}
};
void demo() {
Bad b; // 构造成功
throw 1; // 栈展开 → 析构 b → 抛异常 → terminate
}
正确做法:析构函数内部(包括隐式析构中成员变量的析构)使用 try-catch 吞掉所有异常,或者用 noexcept(false) 明确声明(几乎从不推荐)。
五、异常传播与工具函数
std::terminate
当以下情况发生时调用:
- noexcept 函数抛异常
- 析构函数抛异常且栈展开处于活跃状态
- terminate_handler 自身抛异常
可通过 std::set_terminate 自定义行为(极少需要)。
std::current_exception 与 std::rethrow_exception
用于在非异常路径捕获和延迟重抛异常:
cpp
#include <exception>
std::exception_ptr g_ex;
void worker() {
try {
do_something(); // 可能抛
} catch (...) {
g_ex = std::current_exception(); // 保存当前异常
}
}
void recheck() {
if (g_ex) {
std::rethrow_exception(g_ex); // 延迟重抛
}
}
六、典型面试题
1. 以下代码输出什么?
cpp
void f() noexcept { throw 1; }
int main() { f(); }
答案 :调用 std::terminate,程序崩溃(输出可能是 terminate called after throwing an instance of 'int')。
2. vector 在 push_back 扩容时一定走移动吗?
答案 :不一定。只有当元素的移动构造函数标记为 noexcept 时才走移动,否则走拷贝。这是为了保证强异常安全。
3. 为什么 std::list 的移动构造始终是 O(1)?
答案 :list 是节点式容器,移动操作只需交换头指针,不涉及元素逐个搬迁,且 std::list 的移动构造始终为 noexcept,不存在回滚问题。
4. 如何确认一个类型是否具备 noexcept 移动构造?
cpp
static_assert(std::is_nothrow_move_constructible_v<T>);
5. 以下代码分析异常安全级别:
cpp
void push_back(const T& val) {
if (size() == capacity()) reserve(size() * 2);
new (data_ + size_) T(val);
++size_;
}
答案 :basic 级别。如果第 3 行的 placement new 抛异常,size_ 不变,但 capacity_ 可能已改变(reserve 后),对象处于可用但容量不确定的状态。
6. 以下代码是否有问题?
cpp
std::vector<std::unique_ptr<int>> v;
v.push_back(std::make_unique<int>(42));
答案 :没问题。unique_ptr 的移动构造为 noexcept,vector 在扩容时会使用移动而非拷贝。但如果移动构造抛异常,unique_ptr 的移动会留下空指针,旧缓冲区中的原始对象已被破坏,这就是为什么 vector 拒绝非 noexcept 的移动。
7. 使用 std::current_exception 和直接 catch (...) 抛出有什么不同?
答案 :current_exception 返回 exception_ptr,可以在不同线程或不同栈帧中延迟处理,且不丢失异常类型信息;直接 catch (...) 后再 throw 只能在当前 catch 块内重抛。
8. 有没有办法在 noexcept 函数内部"绕过"terminate?
答案 :不能在 noexcept 函数中向外部传播异常,但可以在函数内部 catch 所有异常并自行处理(如记日志)。函数签名 noexcept 只承诺不对外传播异常,不禁止内部 try-catch。