现代C++实战30讲
吴咏炜
前 Intel 资深软件架构师
立即订阅
3681 人已学习
课程目录
已更新 14 讲 / 共 30 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | C++这么难,为什么我们还要用C++?
免费
课前必读 | 有关术语发音及环境要求
基础篇 (9讲)
01 | 堆、栈、RAII:C++里该如何管理资源?
02 | 自己动手,实现C++的智能指针
03 | 右值和移动究竟解决了什么问题?
04 | 容器汇编 I:比较简单的若干容器
05 | 容器汇编 II:需要函数对象的容器
06 | 异常:用还是不用,这是个问题
07 | 迭代器和好用的新for循环
08 | 易用性改进 I:自动类型推断和初始化
09 | 易用性改进 II:字面量、静态断言和成员函数说明符
提高篇 (3讲)
10 | 到底应不应该返回对象?
11 | Unicode:进入多文字支持的世界
12 | 编译期多态:泛型编程和模板入门
现代C++实战30讲
登录|注册

06 | 异常:用还是不用,这是个问题

吴咏炜 2019-12-09
你好,我是吴咏炜。
到现在为止,我们已经有好多次都提到异常了。今天,我们就来彻底地聊一聊异常。
首先,开宗明义,如果你不知道到底该不该用异常的话,那答案就是该用。如果你需要避免使用异常,原因必须是你有明确的需要避免使用异常的理由。
下面我们就开始说说异常。

没有异常的世界

我们先来看看没有异常的世界是什么样子的。最典型的情况就是 C 了。
假设我们要做一些矩阵的操作,定义了下面这个矩阵的数据结构:
typedef struct {
float* data;
size_t nrows;
size_t ncols;
} matrix;
我们至少需要有初始化和清理的代码:
enum matrix_err_code {
MATRIX_SUCCESS,
MATRIX_ERR_MEMORY_INSUFFICIENT,
};
int matrix_alloc(matrix* ptr,
size_t nrows,
size_t ncols)
{
size_t size =
nrows * ncols * sizeof(float);
float* data = malloc(size);
if (data == NULL) {
return MATRIX_ERR_MEMORY_INSUFFICIENT;
}
ptr->data = data;
ptr->nrows = nrows;
ptr->ncols = ncols;
}
void matrix_dealloc(matrix* ptr)
{
if (ptr->data == NULL) {
return;
}
free(ptr->data);
ptr->data = NULL;
ptr->nrows = 0;
ptr->ncols = 0;
}
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《现代C++实战30讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(13)

  • tt
    文中下面的一句话:

    “首先是内存分配。如果 new 出错,按照 C++ 的规则,一般会得到异常 bad_alloc,对象的构造也就失败了。这种情况下,在 catch 捕捉到这个异常之前,所有的栈上对象会全部被析构,资源全部被自动清理。”

    谈的是new在分配内存时的错误,是堆上内存的错误,但自动被析构的却是栈上的对象。一开始我想是不是笔误了,但仔细想想,堆上的东西都是由栈上的变量所引用的,栈上对象析构的过程,堆上相应的资源自然就被释放了。而且被释放的对象的范围还被栈帧限定了。

    作者回复: 对,这就是 RAII,非常重要。

    学习速度飞快啊。👍

    2019-12-09
    1
    4
  • 中年男子
    用到异常的时候倒不是很多,但是异常千万别乱用,害人害己,
    曾经同事离职,接手他项目的代码,把我坑的,几乎所有能引起crash的地方都用try catch 捕获异常,然而不处理异常,比如非法指针, 这种bug居然用try catch 来规避,坑了我两个月时间才把程序搞稳定了,现在想起他来,心里还有一句mmp想送给他。。。

    作者回复: 任何东西用得不好都是坑。有朋友遇到小项目里用了一大堆(不必要的)设计模式,把代码硬生生弄得不可理解。不能说设计模式就是不好,是不?

    MSVC 可以用 ... 捕获非法指针操作,这也是极易被误用的功能。以前也遇到过一次,一不小心用了这个功能,把明明在调试时可以发现的崩溃变成了程序的怪异行为。不过,严格来讲这不属于 C++ 的异常……这实际上是 Windows 的 SEH,纯 C 里都能做得到。

    2019-12-09
    2
  • Encoded Star
    一、使用异常
    1.异常处理并不意味着需要写显示的try和catch。异常安全的代码,可以没有任何try和catch
    2.适当组织好代码,利用好RAII,实现矩阵的代码和使用矩阵的代码都可以更短、更清晰,处理异常一般情况会记日志或者向外界用户报告错误。
    二、使用异常的理由
    1.vector C++标准容器中提供了at成员函数,能够在下标不存在的时候抛出异常(out_of_range),作为一种额外的帮助调试手段
    2.强异常保证,就是一旦异常发生,现场会恢复到调用异常之前的状态。(vector在元素类型没有提供保证不抛异常的移动构造函数的情况下,在移动元素时会使用拷贝构造函数,一旦某操作发生异常,就可以恢复原来的样子)
    3.只要使用标准容器就都的处理可能引发的异常bad_alloc
    4。可以使用异常,也可以使用assert
    课后思考
    你的C++项目里使用过异常吗?为什么?
    答:按老师课里说的,只要使用了标准容器就得考虑使用处理异常(bad_alloc),所以,大部分C++代码如果保证安全的情况下都的考虑这个异常。当然也在别的地方,之前在读取配置文件(json文件)字段的时候加过,如果读取失败,异常抛出

    作者回复: OK。很好!

    2019-12-22
    1
    1
  • tech2ipo
    老师,你好。目前主流的开源项目中,有没有使用了异常的优秀的C++开源项目?可以用来作为参考案例。

    作者回复: 我不觉得用异常有什么特别的地方,因而用异常的我个人没觉得有什么特别可参考的。

    由于历史原因,有不少大名气的 C++ 程序没有使用异常,特别是 Google 的项目,比如 Chromium。不用异常,实际上是对用户友好(可执行文件略小,性能有可能有小提升),而对开发者更累。

    我知道用到异常的一些项目:

    - Boost
    - C++ REST SDK
    - pytorch
    - pybind11
    - Armadillo
    - nlohmann/json
    - cppcheck
    - OpenCV
    - LibreOffice

    这篇文章也可以看一下:

    https://cppdepend.com/blog/?p=311

    2019-12-13
    1
  • 不谈
    作为java程序员刚开始对于C++这种规则完全不能理解,看了文章之后又理解了,不使用异常是有道理的。现在理解了vector 会在元素类型没有提供保证不抛异常的移动构造函数的情况下,在移动元素时会使用拷贝构造函数这句话的含义了。C++就是这么与众不同

    作者回复: 这种行为是和值语义密切关联的——和 Java 不同。而且,要在构造函数和运算符重载中表达错误,异常是唯一的方法。

    2019-12-17
  • 看到老师说了部分开源的异常优秀的C++开源项目,老师能否推荐些现在流行的,能逐步深入的网络编程方面的C++开源项目看呢,从入门到深入的都推荐一些吧。谢谢老师

    作者回复: 网络就看 Boost.Asio 吧。这个将是未来 C++ 网络标准库的基础。

    2019-12-17
  • 何敬
    请问libbreakpad跟underflow_error有关联么?现在使用breakpad捕获异常,有很多underflow_error的信息

    作者回复: 对不起,我对 libbreakpad 完全不了解。

    2019-12-13
  • 何敬
    请问underflow_error 什么情况下会抛出?

    作者回复: https://zh.cppreference.com/w/cpp/error/underflow_error

    定义作为异常抛出的类型。它可用于报告算术下溢错误(即计算结果是非正规浮点值的情形)。
    标准库组建不抛此异常(数学函数按指定于 math_errhandling 的方式报告下溢错误)。然而第三方库使用它。例如,若启用了 boost::math::policies::throw_on_error (默认设置),则 boost.math 抛出 std::underflow_error 。

    2019-12-11
  • 花晨少年
    C++ 虽然支持运算符重载,可你也不能使用,因为你没法返回一个新矩阵……
    不太理解这句话的意思,是用运算符来替换构造函数吗,运算符为啥不能返回新矩阵?

    作者回复: 因为返回值得留给错误码啊……既然不能用异常。

    2019-12-11
  • neilyu
    C++ 虽然支持运算符重载,可你也不能使用,因为你没法返回一个新矩阵……
    //请问这句话怎么理解?为啥不能返回一个新矩阵呢?

    作者回复: 因为返回值留给错误码了啊……

    2019-12-10
  • 李蔚韬
    老师,对于异常的第一条批评我不太理解,什么叫“只要开启异常,即使不使用”,这里的开启是指什么呢?

    作者回复: GCC/Clang 下的 -fexceptions(缺省开启),MSVC 下的 /EHsc(我要求大家需要用的,Visual Studio 项目里也会自动用)。

    我刚试了,用 GCC,加上 -fno-exceptions 命令行参数,对于下面这样的小程序,也能看到产生的可执行文件的大小的变化。

    #include <vector>

    int main()
    {
        std::vector<int> v{1, 2, 3, 4, 5};
        v.push_back(20);
    }

    2019-12-09
    4
  • 禾桃
    “异常处理并不意味着需要写显式的 try 和 catch。异常安全的代码,可以没有任何 try 和 catch。”

    出现异常时,如果没有任何的try catch,只是让std::terminate, 即使没有资源泄漏之类的,感觉什么也做不了了,感觉还是应该要catch,做点啥,至少得记录一下哪抛出异常,什么异常,然后再主动的std::terminate。

    作者回复: 外围(比如main里)当然是要写catch的。(我们一般也不会主动去调terminate;退出的话一般用exit。)但异常安全的代码本身可以没有任何try和catch。

    学得真快。☺️

    2019-12-09
    1
  • 禾桃
    happy path—-> hot path:)

    作者回复: 不是,就是happy path。愉快的(乐观情况下的)执行路径,而不是说是否频繁。

    2019-12-09
收起评论
13
返回
顶部