C++ STL之异常安全与noexcept详解

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。