异常处理 ######################################## 函数异常块 **************************************** 函数异常块是函数体的一种替代语法,主要用于应对初始化列表抛出的异常,在析构函数或其他常规函数中极少使用: .. code-block:: cpp struct S { std::string m; S(const std::string& arg) try : m(arg, 100) { std::cout << "constructed, mn = " << m << '\n'; } catch(const std::exception& e) { std::cerr << "arg=" << arg << " failed: " << e.what() << '\n'; } // 此处隐式 throw; }; .. seealso:: - `这个语法是怎么回事:int main()try{}catch(){} `_ - `函数 try 块 `_ 异常规范 **************************************** .. seealso:: - :doc:`条款十四:明智利用异常规范 ` - `C++异常处理知多少(二) `_ - `C++11 带来的新特性(3) `_ C++使用跟在函数名后面的 ``throw`` 说明函数会抛出的异常。( ``C++11已废用`` ) 例: .. code-block:: cpp void func1() throw(std::bad_exception, std::bad_alloc); 该异常规范已经在C++11被弃用,但是仍然可能存在于某些代码中。 C++ 还引入了一个新的关键字: noexpect noexpect 有两种用法: - 用于保证函数不会抛出异常。 .. code-block:: cpp void func1() noexcept; 若一个函数被 ``noexcept`` 修饰,但是仍然抛出了异常,则编译器会出现警告。 - 用来判断函数是否被 noexcept 修饰: .. code-block:: cpp noexcept(func1) == true noexcept 表达式的返回值是一个右值 bool 类型。 .. note:: - noexcept 可以用来帮助编译器优化代码 - noexcept 与 throw() 相同,尽管都为函数声明的一部分,但是不允许出现在 typedef 中 - 函数违反了 noexcept 并不会导致调用 unexpected() ,也不会出现 栈解退_ 栈解退 **************************************** 测试代码如下: .. code-block:: cpp #include #include void func1(); void func2(); using namespace std; int main() { try { try { func1(); cout<<"第二层try块"<`_ 异常中断 **************************************** 异常在两种情况下会引发中断: - 若函数引发的异常未被捕获(即 :abbr:`未捕获异常 (uncaught exception)` ),则调用 ``std::terminate()`` 中断程序。 - 若函数引发了不在异常规范列表中的异常(即 :abbr:`意外异常 (unexpected exception)` ),则调用 ``std::unexpected`` 中断程序。 默认情况下 ``std::terminate()`` 将会调用 ``std::abort()`` 中断程序,通过 ``set_terminate()`` 可以自定义 ``std::terminate()`` 调用的函数。例如: .. code-block:: cpp #include #include using namespace std; void myQuit(){ cout<<"myQuit()"< #include using namespace std; void myQuit(){ cout<<"myQuit()"<t_local<OSTOP) , SIGTTOU信号与SIGTTIN很相似,不同之处在于SIGTTOU信号是由于后台进程尝试对一个设置了TOSTOP属性的tty执行写操作时才会 产生。然而,如果tty没有设置这个属性,SIGTTOU就不会被发送。缺省行为是停止进程,直到接收到SIGCONT信号。 SIGIO ,23 , input/output possible signal , 如果进程在一个文件描述符上有I/O操作的话,SIGIO信号将被发送给这个进程。进程可以通过fcntl调用来设置。缺省行为是丢弃该信号 SIGCPU ,24 , exceeded CPU time limit , 如果一旦进程超出了它可以使用的CPU限制(CPU limit),SIGXCPU信号就被发送给它。这个限制可以使用setrlimit设置。缺省行为是终止进程。 SIGXFSZ ,25 , exceeded file size limit , 如果一旦进程超出了它可以使用的文件大小限制,SIGXFSZ信号就被发送给它。稍后我们会继续讨论这个信号。缺省行为是终止进程 SIGVTALRM ,26 , virtual time alarm , 如果一旦进程超过了它设定的虚拟计时器计数时,SIGVTALRM信号就被发送给它。缺省行为是终止进程。 SIGPROF ,27 , profiling time alarm , 当设置了计时器时,SIGPROF是另一个将会发送给进程的信号。缺省行为是终止进程。 SIGWINCH ,28 , window size changes , 当进程调整了终端的尺寸时,SIGWINCH信号被发送给该进程。缺省行为是丢弃该信号。 SIGUSR1 ,29 , user defined signal 1 , 自定义信号,缺省行为是终止进程。 SIGUSR2 ,30 , user defined signal 2 , 自定义信号,缺省行为是终止进程。 .. note:: 信号 0 被用于 ``kill(pid, 0)`` 这个函数。用来在不发送信号的情况下测试进程是否存在 虚拟信号处理函数 ======================================== 除了自定义信号处理函数外,C++还附带了三个虚拟信号处理函数(fake signal functions):[#]_ .. csv-table:: :header: 函数指针, 函数定义, 函数说明, 解释 SIG_ERR , (__sighandler_t) -1) , Error return, 若 ``signal()`` 发生错误,则返回该值 SIG_DFL , (((__sighandler_t) 0)), Default action, 调用信号的默认行为 SIG_IGN , ((__sighandler_t) 1) , Ignore signal, 忽视信号 SIG_HOLD , ((__sighandler_t) 2) , Add signal to hold mask, Add sig to the process’s signal mask, but leave the disposition of sig unchanged. 触发信号 [#]_ ======================================== 使用 ``raise`` 可以向自己的程序发送信号,其声明如下: .. code-block:: cpp int raise (int __sig) throw() 例如: .. code-block:: cpp #include #include int main() { signal(SIGINT, sig2); raise(SIGINT); return 0; } 输出如下:: 信号中断! 进程已结束,退出代码 2 .. [#] `关于typedef void (*sighandler_t)(int)的理解 `_ .. [#] `C++ signal的使用 `_ .. [#] `sighold,sigset,sigrelse `_ .. [#] `C++信号处理 `_ 零成本异常并不是零成本 **************************************** .. admonition:: 译自 `Zero-cost exceptions aren't actually zero cost `_ C++ 中有两种常见的异常处理模型。一种是在异常方式时通过做一系列工作来更新程序状态。例如,进入异常处理作用域或者退出作用域。另一种模型是使用元数据来描述发生异常是应该做什么,运行时没有对状态的显式管理。相反,异常机制通过查看程序计数器和查询元数据来推断状态 基于元数据的异常处理通常被 `误导性地称为零成本异常 `_ ,这听起来像是异常没有成本。事实上完全相反,基于元素的异常应当被称为 *超级昂贵的异常* 基于元数据的异常处理的要点是:在主线(非异常)代码路径中没有用于异常支持的代码,异常发生的次数很少,因此您最终会得到更好的性能。 +----------+------------------------------+--------------------------------------------------------------------+ | 模式 | 运行时管理 | 基于元数据 | +==========+==============================+====================================================================+ | 主线代码 | 在运行时更新状态 | | +----------+------------------------------+--------------------------------------------------------------------+ | 发生异常 | 查询状态以找到正确的处理程序 | 获取程序计数器并找到适用它的元数据,查询元数据以找到正确的处理程序 | +----------+------------------------------+--------------------------------------------------------------------+ 请注意:使用基于元数据的所谓”零成本“异常实际上会导致抛出异常的成本显著增加,因为抛出异常的机器必须找到元数据以便查找要运行的处理程序。此元数据通常以 `针对大小而非速度的格式 `_ 储存,因此必须在抛出异常时进行额外的工作以解码数据来找到正确的处理程序 “零成本异常”的名字意指没有生成额外的代码来防止异常发生 但即使是这样,也并不是零成本异常就和没有异常一样 异常的存在意味着代码生成受制于隐式约束:在执行任何可能引发异常的操作之前,如果对象对于一个异常处理是可见的,那么编译器必须将对象状态储存到内存中。(任何带有遇析构函数的对象都是可见的,因为异常处理程序可能必须运行析构函数) 简单来讲,潜在的可抛出异常操作限制了编译器优化可观察对象的能力,因为异常流和主线代码是相互独立的 这些成本是肉眼不可见的。他们会导致失去优化的机会 零成本例外很好(尽管用词不当),但请注意,成本实际上并不为零。