断言(assertion)是一种编程中常用的手段。相比于断言适用于排除逻辑上不可能存在的状态,异常通常是用于逻辑上可能发生的错误。在C++98中,我们看到了一套完整的不同于C的异常处理系统。通过这套异常处理系统,C++拥有了远比C强大的异常处理功能。下面来一起了解一下吧。
C++11开始,我们能看到很多代码当中都有关键字noexcept。比如下面就是std::initializer_list的默认构造函数,其中使用了noexcept。
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) { }
该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
在异常处理的代码中,程序员有可能看到过如下的异常声明表达形式:
void excpt_func()throw(int,double){...}
在excpt_func函数声明之后,我们定义了一个动态异常声明throw(int,double),该声明指出了excpt_func可能抛出的异常的类型。
事实上,该特性很少被使用,因此在C++11中被弃用了,而表示函数不会抛出异常的动态异常声明throw()也被新的noexcept异常声明所取代。
noexcept形如其名地,表示其修饰的函数不会抛出异常。
不过与throw()动态异常声明不同的是,在C++11中如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行,这比基于异常机制的throw()在效率上会高一些。
这是因为异常机制会带来一些额外开销,比如函数抛出异常,会导致函数栈被依次地展开(unwind),并依帧调用在本帧中已构造的自动变量的析构函数等。
从语法上讲,noexcept修饰符有两种形式,一种就是简单地在函数声明后加上noexcept关键字。比如:
void excpt_func()noexcept;
另外一种则可以接受一个常量表达式作为参数,如下所示:
void excpt_func()noexcept(常量表达式);
常量表达式的结果会被转换成一个bool类型的值。该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。这里,不带常量表达式的noexcept相当于声明了noexcept(true),即不会抛出异常。
在通常情况下,在C++11中使用noexcept可以有效地阻止异常的传播与扩散。我们可以看看下面这个例子,如下面的代码清单所示。
#include <iostream>
using namespace std;
void Throw(){throw 1;}
void NoBlockThrow(){Throw();}
void BlockThrow()noexcept{Throw();}
int main(){
try{
Throw();
}
catch(...){
cout<<"Found throw."<<endl;//Found throw.
}
try{
NoBlockThrow();
}
catch(...){
cout<<"Throw is not blocked."<<endl;//Throw is not blocked.
}
try{
BlockThrow();//terminate called after throwing an instance of'int'
}
catch(...){
cout<<"Found throw 1."<<endl;
}
}
//编译选项:g++ -std=c++11 2-6-1.cpp
在上面的代码清单中,我们定义了Throw函数,该函数的唯一作用是抛出一个异常。而NoBlockThrow是一个调用Throw的普通函数,BlockThrow则是一个noexcept修饰的函数。
从main的运行中我们可以看到,NoBlockThrow会让Throw函数抛出的异常继续抛出,直到main中的catch语句将其捕捉。而BlockThrow则会直接调用std::terminate中断程序的执行,从而阻止了异常的继续传播。从使用效果上看,这与C++98中的throw()是一样的。
其实就这么多东西。你用过的任何程序,不管多么复杂,都是由上面这些基本功能组合而成的。所以我们也可以说,编程是将复杂的任务逐步分解为越来越小的子任务,直到问题解决,看完你学会了吗?