乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

noexcept 与异常安全

定义与作用

C++11 引入 noexcept 替代弃用的动态异常规范(throw())。noexcept 有两个角色:

  • noexcept 说明符:声明函数不抛异常
  • noexcept 运算符:编译期检测表达式是否会抛异常
void safe_func() noexcept;              // 声明不抛异常
bool b = noexcept(safe_func());         // 编译期检测:true

异常安全(Exception Safety)是 C++ 程序健壮性的核心保证——即使在异常发生时,程序状态仍可预期。

核心原理

noexcept 说明符与移动语义的关系

三级异常安全保证

完整示例

示例一:noexcept 对性能的影响

场景说明:对比移动构造加不加 noexcept 对 std::vector 扩容行为的影响。

#include <iostream>
#include <vector>
#include <chrono>
#include <string>

// 不带 noexcept 的移动构造
struct NoNoexcept {
    std::string data;
    NoNoexcept(std::string s) : data(std::move(s)) {
        std::cout << "  构造: " << data << std::endl;
    }
    NoNoexcept(const NoNoexcept& other) : data(other.data) {
        std::cout << "  拷贝: " << data << std::endl;
    }
    NoNoexcept(NoNoexcept&& other)  // 故意不加 noexcept
        : data(std::move(other.data)) {
        std::cout << "  移动: " << data << std::endl;
    }
};

// 带 noexcept 的移动构造
struct YesNoexcept {
    std::string data;
    YesNoexcept(std::string s) : data(std::move(s)) {
        std::cout << "  构造: " << data << std::endl;
    }
    YesNoexcept(const YesNoexcept& other) : data(other.data) {
        std::cout << "  拷贝: " << data << std::endl;
    }
    YesNoexcept(YesNoexcept&& other) noexcept  // 加了 noexcept
        : data(std::move(other.data)) {
        std::cout << "  移动: " << data << std::endl;
    }
};

// ====== noexcept 运算符的编译期检测 ======

void may_throw() {}                // 可能抛异常
void never_throws() noexcept {}    // 绝不抛异常

template <typename T>
void check_noexcept() {
    std::cout << typeid(T).name() << " 移动构造 noexcept? "
              << std::boolalpha
              << noexcept(T(std::declval<T&&>()))  // noexcept 运算符
              << std::endl;
}

int main() {
    std::cout << "===== noexcept 运算符 =====" << std::endl;
    std::cout << "may_throw() noexcept?    " << noexcept(may_throw()) << std::endl;
    std::cout << "never_throws() noexcept? " << noexcept(never_throws()) << std::endl;
    std::cout << "1 + 1 noexcept?          " << noexcept(1 + 1) << std::endl;

    std::cout << "\n===== 类型检测 =====" << std::endl;
    check_noexcept<NoNoexcept>();
    check_noexcept<YesNoexcept>();
    check_noexcept<std::string>();
    check_noexcept<int>();

    std::cout << "\n===== vector 扩容:无 noexcept =====" << std::endl;
    {
        std::vector<NoNoexcept> vec;
        vec.emplace_back("A");
        std::cout << "--- 添加第二个元素(触发扩容)---" << std::endl;
        vec.emplace_back("B");  // 扩容时使用拷贝而非移动!
    }

    std::cout << "\n===== vector 扩容:有 noexcept =====" << std::endl;
    {
        std::vector<YesNoexcept> vec;
        vec.emplace_back("X");
        std::cout << "--- 添加第二个元素(触发扩容)---" << std::endl;
        vec.emplace_back("Y");  // 扩容时使用移动!
    }

    return 0;
}

预期输出:

===== noexcept 运算符 =====
may_throw() noexcept?    false
never_throws() noexcept? true
1 + 1 noexcept?          true

===== 类型检测 =====
...
===== vector 扩容:无 noexcept =====
  构造: A
--- 添加第二个元素(触发扩容)---
  构造: B
  拷贝: A           ← 用拷贝而不用移动!
===== vector 扩容:有 noexcept =====
  构造: X
--- 添加第二个元素(触发扩容)---
  构造: Y
  移动: X           ← 用移动!

逐段分析:

  • noexcept(表达式) 在编译期返回 bool——表达式是否会抛异常
  • 不带 noexcept 的移动构造 → vector 扩容回退到拷贝——拷贝安全但慢
  • 带 noexcept 的移动构造 → vector 扩容使用移动——移动高效
  • int 的移动天生 noexcept——所有基本类型的移动操作都 noexcept

示例二:异常安全的银行转账

#include <iostream>
#include <string>
#include <stdexcept>
#include <map>

class BankAccount {
public:
    BankAccount(std::string owner, double balance)
        : owner_(std::move(owner)), balance_(balance) {}

    // 转账——强异常安全保证
    void transfer(BankAccount& to, double amount) {
        if (this == &to) return;

        // 必要检查(不涉及状态修改)
        if (amount <= 0)
            throw std::invalid_argument("转账金额必须为正数");
        if (balance_ < amount)
            throw std::runtime_error(owner_ + " 余额不足");

        // 先修改对方账户(可能抛异常)
        to.balance_ += amount;

        // 再修改自己账户(不抛异常)
        balance_ -= amount;

        std::cout << "  转账: " << owner_ << " → " << to.owner_
                  << " ¥" << amount << std::endl;
    }

    void deposit(double amount) {
        if (amount <= 0)
            throw std::invalid_argument("存款金额必须为正数");
        balance_ += amount;
    }

    void withdraw(double amount) {
        if (amount <= 0)
            throw std::invalid_argument("取款金额必须为正数");
        if (balance_ < amount)
            throw std::runtime_error("余额不足");
        balance_ -= amount;
    }

    void print() const {
        std::cout << "  " << owner_ << ": ¥" << balance_ << std::endl;
    }

    // 获取余额——noexcept 保证
    double balance() const noexcept { return balance_; }

    // swap 操作——noexcept
    void swap(BankAccount& other) noexcept {
        std::swap(owner_, other.owner_);
        std::swap(balance_, other.balance_);
    }

private:
    std::string owner_;
    double balance_;
};

int main() {
    std::cout << "===== 正常转账 =====" << std::endl;

    BankAccount alice("张三", 5000);
    BankAccount bob("李四", 2000);
    alice.print();
    bob.print();

    try {
        alice.transfer(bob, 1000);
        std::cout << "转账成功" << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "转账失败: " << e.what() << std::endl;
    }

    alice.print();
    bob.print();

    std::cout << "\n===== 异常场景 =====" << std::endl;

    // 场景 1:余额不足
    try {
        bob.transfer(alice, 100000);
    } catch (const std::runtime_error& e) {
        std::cerr << "失败: " << e.what() << std::endl;
    }

    // 场景 2:负数转账
    try {
        alice.transfer(bob, -100);
    } catch (const std::invalid_argument& e) {
        std::cerr << "失败: " << e.what() << std::endl;
    }

    std::cout << "\n最终状态:" << std::endl;
    alice.print();
    bob.print();

	std::cout << "李四余额 noexcept? true" << std::endl;

    return 0;
}

预期输出:

===== 正常转账 =====
  张三: ¥5000
  李四: ¥2000
  转账: 张三 → 李四 ¥1000
转账成功
  张三: ¥4000
  李四: ¥3000

===== 异常场景 =====
失败: 李四 余额不足
失败: 转账金额必须为正数

最终状态:
  张三: ¥4000
  李四: ¥3000
李四余额 noexcept? true

易错场景与面试考点

易错场景

场景错误表现正确做法
移动构造/赋值不加 noexceptSTL 容器回退到拷贝所有移动操作标记 noexcept
noexcept 函数中抛异常直接 terminate确保 noexcept 函数内 catch 所有异常
修改状态后再抛异常不满足强异常保证先做不修改状态的检查,再改对方,最后改自己
swap 不是 noexceptcopy-and-swap 不安全swap 必须 noexcept

常见面试问题

  1. 为什么移动构造应该标记 noexcept?——STL 容器(如 std::vector)在扩容等操作时,如果元素移动构造不是 noexcept,会回退到拷贝构造以保证异常安全。标记 noexcept 让容器放心使用更高效的移动语义。

  2. 三级异常安全保证分别是什么?——(1) 不抛出保证(noexcept:绝无异常);(2) 强保证(操作成功或状态不变,如 copy-and-swap);(3) 基本保证(不泄漏资源,对象处于有效但可能改变的状态)。

  3. noexcept 运算符的用途?——编译期检测表达式是否会抛异常。常用于模板代码:noexcept(T(std::declval<T&&>())) 检测移动构造是否 noexcept。

  4. noexcept(true) 和 noexcept(false) 的区别?——noexcept 等价于 noexcept(true),声明不抛异常。noexcept(false) 等价于不写。noexcept 接受编译期 bool 表达式:noexcept(cond)。

  5. 析构函数为什么隐式 noexcept?——C++11 起析构函数默认 noexcept,因为栈展开期间析构函数抛异常会导致 std::terminate。绝对不要在析构函数中抛异常。

小结

  • noexcept 说明符声明函数不抛异常——违反则 terminate
  • noexcept 运算符编译期检测表达式异常性——模板元编程利器
  • 移动构造/赋值必须标记 noexcept——直接影响 STL 容器的性能策略
  • 三级异常安全保证:noexcept → 强保证 → 基本保证
  • 析构函数隐式 noexcept——RAII 异常安全的最后防线
上一页
异常处理机制
下一页
RAII 与异常安全实践