C++11 noexcept 关键字用法学习

2023-11-14

最近学习和写了一个 mint 的板子 ,其中用到了 noexcept 关键字,对这个关键字不太熟悉,便学习一下刘毅学长的文章。

C++98 中的异常规范(Exception Specification)

傲芙小说网 https://www.3973.info

throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范,有些教程也称为异常指示符或异常列表。请看下面的例子:

double func1 (char param) throw(int);

函数 func1 只能抛出 int 类型的异常。如果抛出其他类型的异常,try 将无法捕获,并直接调用 std::unexpected。

如果函数会抛出多种类型的异常,那么可以用逗号隔开,

double func2 (char param) throw(int, char, exception);

如果函数不会抛出任何异常,那么只需写一个空括号即可,

double func3 (char param) throw();

同样的,如果函数 func3 还是抛出异常了,try 也会检测不到,并且也会直接调用 std::unexpected。

虚函数中的异常规范

C++ 规定,派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格,或者更严格。只有这样,当通过基类指针(或者引用)调用派生类虚函数时,才能保证不违背基类成员函数的异常规范。请看下面的例子:

class Base {
  public:
    virtual int fun1(int) throw();
    virtual int fun2(int) throw(int);
    virtual string fun3() throw(int, string);
};

class Derived: public Base {
  public:
    int fun1(int) throw(int);    //错!异常规范不如 throw() 严格
    int fun2(int) throw(int);    //对!有相同的异常规范
    string fun3() throw(string); //对!异常规范比 throw(int, string) 更严格
}

异常规范与函数定义和函数声明

C++ 规定,异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。请看下面的几组函数:

// 错!定义中有异常规范,声明中没有
void func1();
void func1() throw(int) { }

// 错!定义和声明中的异常规范不一致
void func2() throw(int);
void func2() throw(int, bool) { }

// 对!定义和声明中的异常规范严格一致
void func3() throw(float, char *);
void func3() throw(float, char *) { }

异常规范在 C++11 中被摒弃

异常规范的初衷是好的,它希望让程序员看到函数的定义或声明后,立马就知道该函数会抛出什么类型的异常,这样程序员就可以使用 try-catch 来捕获了。如果没有异常规范,程序员必须阅读函数源码才能知道函数会抛出什么异常。

不过这有时候也不容易做到。例如,func_outer() 函数可能不会引发异常,但它调用了另外一个函数 func_inner(),这个函数可能会引发异常。再如,编写的一个函数调用了老式的一个库函数,此时不会引发异常,但是老式库更新以后这个函数却引发了异常。

其实,不仅仅如此,

  1. 异常规范的检查是在运行期而不是编译期,因此程序员不能保证所有异常都得到了 catch 处理。

  2. 由于第一点的存在,编译器需要生成额外的代码,在一定程度上妨碍了优化。

  3. 模板函数中无法使用。比如下面的代码,

    template<class T>
    void func(T k) {
        T x(k);
        x.do_something();
    }
    

    赋值函数、拷贝构造函数和 do_something() 都有可能抛出异常,这取决于类型 T 的实现,所以无法给函数 func 指定异常类型。

  4. 实际使用中,我们只需要两种异常说明:抛异常和不抛异常,也就是 throw(...) 和 throw()。

所以 C++11 摒弃了 throw 异常规范,而引入了新的异常说明符 noexcept。

C++11 noexcept

noexcept 紧跟在函数的参数列表后面,它只用来表明两种状态:"不抛异常" 和 "抛异常"。

void func_not_throw() noexcept; // 保证不抛出异常
void func_not_throw() noexcept(true); // 和上式一个意思

void func_throw() noexcept(false); // 可能会抛出异常
void func_throw(); // 和上式一个意思,若不显示说明,默认是会抛出异常(除了析构函数,详见下面)

对于一个函数而言,

  1. noexcept 说明符要么出现在该函数的所有声明语句和定义语句,要么一次也不出现。
  2. 函数指针及该指针所指的函数必须具有一致的异常说明。
  3. 在 typedef 或类型别名中则不能出现 noexcept。
  4. 在成员函数中,noexcept 说明符需要跟在 const 及引用限定符之后,而在 final、override 或虚函数的 =0 之前。
  5. 如果一个虚函数承诺了它不会抛出异常,则后续派生的虚函数也必须做出同样的承诺;与之相反,如果基类的虚函数允许抛出异常,则派生类的虚函数既可以抛出异常,也可以不允许抛出异常。

需要注意的是,编译器不会检查带有 noexcept 说明符的函数是否有 throw

void func_not_throw() noexcept {
    throw 1; // 编译通过,不会报错(可能会有警告)
}

这会发生什么呢?程序会直接调用 std::terminate,并且不会栈展开(Stack Unwinding)(也可能会调用或部分调用,取决于编译器的实现)。另外,即使你有使用 try-catch,也无法捕获这个异常。

#include <iostream>
using namespace std;

void func_not_throw() noexcept {
    throw 1;
}

int main() {
    try {
        func_not_throw(); // 直接 terminate,不会被 catch
    } catch (int) {
        cout << "catch int" << endl;
    }
    return 0;
}

所以程序员在 noexcept 的使用上要格外小心!

noexcept 除了可以用作说明符(Specifier),也可以用作运算符(Operator)。noexcept 运算符是一个一元运算符,它的返回值是一个 bool 类型的右值常量表达式,用于表示给定的表达式是否会抛出异常。例如,

void f() noexcept {
}

void g() noexcept(noexcept(f)) { // g() 是否是 noexcept 取决于 f()
    f();
}

其中 noexcept(f) 返回 true,则上式就相当于 void g() noexcept(true)

析构函数默认都是 noexcept 的。C++ 11 标准规定,类的析构函数都是 noexcept 的,除非显示指定为 noexcept(false)

class A {
  public:
    A() {}
    ~A() {} // 默认不抛出异常
};

class B {
  public:
    B() {}
    ~B() noexcept(false) {} // 可能会抛出异常
};

在为某个异常进行栈展开的时候,会依次调用当前作用域下每个局部对象的析构函数,如果这个时候析构函数又抛出自己的未经处理的另一个异常,将会导致 std::terminate。所以析构函数应该从不抛出异常。

显示指定异常说明符的益处

  1. 语义

    从语义上,noexcept 对于程序员之间的交流是有利的,就像 const 限定符一样。

  2. 显示指定 noexcept 的函数,编译器会进行优化

    因为在调用 noexcept 函数时不需要记录 exception handler,所以编译器可以生成更高效的二进制码(编译器是否优化不一定,但理论上 noexcept 给了编译器更多优化的机会)。另外编译器在编译一个 noexcept(false) 的函数时可能会生成很多冗余的代码,这些代码虽然只在出错的时候执行,但还是会对 Instruction Cache 造成影响,进而影响程序整体的性能。

  3. 容器操作针对 std::move 的优化

    举个例子,一个 std::vector<T>,若要进行 reserve 操作,一个可能的情况是,需要重新分配内存,并把之前原有的数据拷贝(copy)过去,但如果 T 的移动构造函数是 noexcept 的,则可以移动(move)过去,大大地提高了效率。

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class A {
      public:
        A(int value) {
        }
    
        A(const A &other) {
            std::cout << "copy constructor
    ";
        }
    
        A(A &&other) noexcept {
            std::cout << "move constructor
    ";
        }
    };
    
    int main() {
        std::vector<A> a;
        a.emplace_back(1);
        a.emplace_back(2);
    
        return 0;
    }
    

    上述代码可能输出:

    move constructor
    

    但如果把移动构造函数的 noexcept 说明符去掉,则会输出:

    copy constructor
    

    你可能会问,为什么在移动构造函数是 noexcept 时才能使用?这是因为它执行的是 Strong Exception Guarantee,发生异常时需要还原,也就是说,你调用它之前是什么样,抛出异常后,你就得恢复成啥样。但对于移动构造函数发生异常,是很难恢复回去的,如果在恢复移动(move)的时候发生异常了呢?但复制构造函数就不同了,它发生异常直接调用它的析构函数就行了。

使用建议

我们所编写的函数默认都不使用,只有遇到以下的情况你再思考是否需要使用,

  1. 析构函数

    这不用多说,必须也应该为 noexcept。

  2. 构造函数(普通、复制、移动),赋值运算符重载函数

    尽量让上面的函数都是 noexcept,这可能会给你的代码带来一定的运行期执行效率。

  3. 还有那些你可以 100% 保证不会 throw 的函数

    比如像是 int,pointer 这类的 getter,setter 都可以用 noexcept。因为不可能出错。但请一定要注意,不能保证的地方请不要用,否则会害人害己!切记!如果你还是不知道该在哪里用,可以看下准标准库 Boost 的源码,全局搜索 BOOST_NOEXCEPT,你就大概明白了。

参考

  • C++ throw 关键字(抛出异常+异常规范)
  • Deprecated throw-list in C++11
  • Exceptions
  • C++11的noexcept标识符与操作符应如何正确使用?
  • When noexcept?
  • Why does reallocating a vector copy instead of moving the elements?
  • When should I really use noexcept?
  • Rvalue References and Exception Safety
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11 noexcept 关键字用法学习 的相关文章

  • Redis详解

    1 键值数据库的基本架构 不同键值数据库支持的key类型一般差异不大 而value类型则有较大差别 我们在对键值数据库进行选型时 一个重要的考虑因素是它支持的value类型 例如 Memcached支持的value类型仅为String类型
  • C语言小游戏——贪吃蛇

    贪吃蛇 1 游戏界面 2 游戏说明 3 程序源代码 1 游戏界面 2 游戏说明 贪吃蛇游戏按键说明 按方向键上下左右 可以实现蛇移动方向的改变 3 程序源代码 代码如下 示例 include
  • unity 3D RPG高级教程(十四)

    目录 声明 1 QuestGiver 控制任务对话显示 2 GiveRewards 拿到任务奖励 3 SaveQuestManager 保存任务数据 声明 本教程学习均来自U3D中文课堂麦扣老师 1 QuestGiver 控制任务对话显示
  • 层数最深的叶子节点之和

    题目描述 给你一棵二叉树的根节点 root 请你返回 层数最深的叶子节点的和 解题思路 使用深度优先搜索 全局维护两个变量sum 总和 以及maxdeep 最大深度 对于遍历到的节点有三种情况 1 此节点深度不够 不进行操作 遍历它的子节点
  • 浏览器地址栏输入url回车后发生了什么

    面试第一天 面试官问我 浏览器输入url后怎么进行请求的 我一想 诶 回车不就向后台发送请求就行了吗 卒 当按下回车后发生了以下步骤 1 ip地址解析寻址 2 建立tcp连接 三次握手 3 发送一个http请求 4 服务器处理并返回响应请求
  • 机器人操作系统ROS(22)ROS安装opencv

    应该是再安装ROS时 已经默认安装opencv 但是有时候需要的环境不同 下面两种方法 第一种是 再安装一个版本 两个版本共存 第二种是 删除再安装 还有一种是Linux下安装OpenCV4 适用于Ubuntu等 还有一个知乎版本用树莓派4
  • Python——os.mkdir()在指定路径下创建文件夹 + 路径的连接理解

    引子 我在用路径连接函数 os path join 时发现 其连接的各级目录必须首先存在 才可以连接 也即是说连接的各文件夹必须首先存在 因为个各层级的目录以文件夹的形式表现出来 基于此 我开始研究如何先在指定路径下创建文件夹 1 在指定路
  • linux批量替换文件夹中所有文件内容

    记录一下 以防自己忘了 替换单个文件 sed i s 旧字符串 新字符串 g 文件名 替换整行 sed i 旧字符串 c 新字符串 文件名 替换文件夹中所有文件内容 sed i s 旧字符串 新字符串 g grep 旧字符串 rl 目录
  • MIPI协议之CSI2和DPHY

    目录 1 概论 2 CSI2协议 2 1 CSI 2数据帧格式 3 D PHY 3 1 数据通道模块的工作流程图 3 2 数据通道模块状态图 1 概论 MIPI协议 Moblie Industry Process Interface 移动行
  • Android 如何修改按钮默认的讨厌的蓝紫色

    1 在设置好按钮背景时 发现钮颜色始终没有改变 2 原来是默认主题themes的问题 在这里修改主题即可 3 找到 res values themes themes xml 双击打开themes xml文件 4 修改parent内容为 Th
  • Linux编程语言glob函数,linux glob函数man页与实例

    Linux Programmer s Manual NAME glob globfree find pathnames matching a pattern free memory from glob SYNOPSIS include in
  • 如何选择IO调度器

    概述 不格文学网 m vbuge com 由于对multi quque的IO调度算法不太熟悉 为了避免误人子弟 本文暂时只会介绍如何选择single queue的IO调度算法 等将来对multi queue有充分认识后再补充 如果不清楚什么
  • redhat激活管理

    redhat激活管理 redhat激活管理常用命令 查看 激活 删除订阅 刷新 redhat激活管理常用命令 https blog csdn net xixihahalelehehe article details 79108442 查看
  • pyqt5_1 Qt Designer组件讲解

    一 布局 Vertical Layout 纵向布局 Horizontal Layout 横向布局 Grid Layout 栅格布局 QGridLayout 网格布局 是将窗口分割成行和列的网络来进行排列 Form Layout 表单布局 在
  • 关于U盘中“文件夹EXE病毒”的解决方案

    笔者在使用U盘时 无意之间发现U盘所有文件的后缀名均变为 exe 经过查询相关资料 确认这是一种病毒 文件夹EXE病毒 一 简介 木马名称 Worm Win32 AutoRun soq 当把U盘插入到一台电脑后 U盘内生成了以原文件夹名字命
  • Ubuntu 安装 Oracle JDK

    1 写在前面 本文主要介绍如何在Ubuntu系统下安装Oracle JDK 2 环境准备 2 1 下载JDK 2 1 1 浏览器下载安装包 进入虚拟机浏览器访问官网地址 http www oracle com technetwork jav
  • 一分钟带你快速认识S参数

    S 参数是SI与RF领域工程师必备的基础知识 大家很容易从网络或书本上找到S Y Z参数的说明 但即使如此 在相关领域打滚多年的人 仍然可能还是会被一些问题困扰着 你懂S参数吗 不懂的话 那么请继续往下看 S参数简介 S参数 也就是散射参数
  • GitLab的使用 和 Git 、 Github、Gitlab的区别

    一 git github gitlab的区别 百度相关内容得到的理解 二 git最基本作用 版本控制 三 有集成了git的GIT安装包 github和gitlab都使用git该版本控制系统 来实现对代码的管理 所以 原先怎么用git操作gi
  • obs窗口捕获不到ppt白屏_如何用obs进行电脑直播,学会这篇,直播不再难

    很多人想在头条或者西瓜视频直播 除了用手机直播外 还可以用电脑进行直播 只要用obs进行简单设置即可达到要求 可以直播ppt 直播ps等 1 下载并安装好obs软件 点击文件 设置 在设置的窗口中 找到输出 一般输出的设置默认就好 无需更改
  • 微信浮窗是不是服务器保存,微信浮窗,真能解决小程序留存难题吗?

    小程序浮窗 到底有多大能量 作者丨Suvi 上个月 微信更新了7 0 5版本 对浮窗功能做了全新升级 支持最多同时添加5个项目 不含QQ音乐 并首次支持添加小程序 新版浮窗一上线 便被寄予厚望 各方将之解读为挽救公众号阅读量 提高小程序留存

随机推荐

  • 【8】测试用例设计-边界值法

    对于软件来说 错误经常发生在输入或输出值的关键点 边界值分析法是对软件的输入或输出边界进行测试的一种方法 它的所有测试用例都是在等价类的边界处设计 边界值分析需要选择一个或多个元素 以便等价类的每个边界都经过一次测试 与仅仅关注输入条件 输
  • QT+CUDA混合编程BUG(一)

    QT CUDA混合编程BUG 一 在QT中进行CUDA编程 CUDA库与其他外部库冲突 debug失败 问题描述 在QT中进行CUDA编程 单独使用CUDA编程时并未出现难以解决的问题 但当我讲CUDA处理的部分 加入已搭建完毕一项较大的Q
  • LeetCode第 292 题:Nim游戏(C++)

    292 Nim 游戏 力扣 LeetCode 剩下4块的时候 如果轮到你 那么你必输 先简单推一下 如果第n块的时候轮到你 n 5 必胜 拿1块 n 6 必胜 拿2块 n 7 必胜 拿3块 n 8 必败 无论我拿几块 对方都可以将我逼到4的
  • 基于Lasagne实现限制玻尔兹曼机(RBM)

    RBM理论部分大家看懂这个图片就差不多了 Lasagne写代码首先要确定层与层 RBM 正向反向过程可以分别当作一个层 权值矩阵互为转置即可 代码 coding utf 8 data format is bc01 written by Ph
  • 【Shell编程】Shell中Bash变量-用户自定义变量

    目录 系列文章 Bash变量 用户自定义变量 变量的命名规则 变量分类 本地变量 实例 系列文章 Shell编程 Shell基本概述与脚本执行方式 Shell编程 Shell中Bash基本功能 Bash变量 用户自定义变量 变量的命名规则
  • 前端跨域解决方案

    目录 同源政策 跨域 常见的跨域场景 跨域解决方案 1 JSONP跨域 1 原生JS实现 2 jquery Ajax实现 3 Vue axios实现 后端node js代码 2 跨域资源共享 CORS 1 简单请求 2 非简单请求 3 CO
  • 满载大模型技能干货的AI Day活动全新来袭

    AI大模型时代 创造力才是第一生产力 满载大模型技能干货的AI Day主题活动全新来袭 丰富有趣的Workshop即将空降你的学校 帮助大家掌握前沿技能 拓展技术视野 迈进AIGC的大门 打造属于你的AI应用 满足不同阶段的学习实践需求 无
  • AD10软件打不开,停留在开机界面上

    解决办法 把AD10的缓存文件都删掉 C Users Administrator AppData Roaming Altium下的文件都删掉
  • 二分图最大完美匹配

    嗯 想不通 就是二分之后的点 寻找左边的点和右边的点的保证两条边的顶点不相同的最大边数 匈牙利算法 O mn 左边寻找和右边相邻的边 如果右边还没有和左边进行连线 那么匹配成功 如果右边已经进行连线 那么考虑左边是否能更改连线 换一个右边
  • Qt总结——菜单隐藏

    我们在使用QMenu的时候经常会在其中添加子菜单以及action QMenu中的隐藏禁用的操作是针对action的 所以直接操作action是没有问题的可以想要的效果 但是menu不行 因为不是action类型的 QAction actio
  • 元组:(tuple)

    1 元组 tuple tuple 相比较列表list 元组和列表都是一种有序集合 0 2 3 5 9 第一个元素即是第一个存入的元素 按照存放顺序存储 元组的访问 格式 元组名 下标 tuple4 1 2 3 4 5 print tuple
  • python从视频信息中提取音频,只用了三行代码...

    在做多媒体素材的时候 往往需要从视频中提取音频信息 在python中提供了moviepy这个非标准库 可以很快的帮助我们完成这个操作 算上导入moviepy非标准库的操作只需要三行代码完成 喜欢记得收藏 关注 点赞 1 单文件处理 将mov
  • Java 性能优化的 50 个细节

    前言 在JAVA程序中 性能问题的大部分原因并不在于JAVA语言 而是程序本身 养成良好的编码习惯非常重要 能够显著地提升程序性能 1 尽量在合适的场合使用单例 使用单例可以减轻加载的负担 缩短加载的时间 提高加载的效率 但并不是所有地方都
  • MySQL 表的 添加、更新与删除数据

    添加数据 为表中的所有 部分字段 添加数据 可以添加部分 也可以全部添加 INSERT INTO 表名称 字段名 VALUES 数据类型对应的 值 指定的字段 或 全部字段添加的数据 INSERT INTO 表名称 SET 字段名 值 同时
  • 用python实现时间序列自相关图(acf)、偏自相关图(pacf)

    自相关图是一个平面二维坐标悬垂线图 横坐标表示延迟阶数 纵坐标表示自相关系数 偏自相关图跟自相关图类似 横坐标表示延迟阶数 纵坐标表示偏自相关系数 自相关图与偏自相关图的python代码实现 from statsmodels graphic
  • 一个对象赋予另一个对象

    它的两个对象 n1 和n2 是在 main 里创建的 每个对象中的i 值都赋予了一个不 同的值 随后 将 n2 赋给n1 而且 n1 发生改变 当给n1 i赋值时 n2 i也会随着改变 这是由于无论n1 还是n2 都包含了相同的句柄 它指向
  • stm32cubeide驱动LCD1602显示屏

    STM32驱动LCD1602 硬件连接关系 STM32CUBEIDE设置 代码 项目设置 最后运行 硬件连接关系 LCD1602 STM32 VCC VCC GND GND VO VCC 滑动变阻 RS PB1 RW PB2 BOOT1 E
  • 上海国际区块链赋能传统产业峰会-王伟:道道人才链启动

    上海国际区块链赋能传统产业峰会 王伟 道道人才链启动 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 经信研究 国研智库 国情讲坛 万赢信采编 在当前 区块链成为全球技术发展的前沿阵地 谋定研究 对话中国经济和信息化 区块链将加速 可信
  • QPixmap的尺寸设置

    问题描述 在控件上使用QPixmap时 希望能重新修改图片的分辨率 而不是使用图片的原始分辨率 解决方法 主要有两种方法 设置控件的尺寸 例如QPixmap被传入一个QLabel控件时 可以通过设置QLabel控件的尺寸来对QPixmap进
  • C++11 noexcept 关键字用法学习

    最近学习和写了一个 mint 的板子 其中用到了 noexcept 关键字 对这个关键字不太熟悉 便学习一下刘毅学长的文章 C 98 中的异常规范 Exception Specification 傲芙小说网 https www 3973 i