• 后面的文章我们将介绍throw异常说明(javascript:void(0)),其实noexcept异常说明和throw异常说明差不多,只是C++定义了一个更加完善的标准
一、为什么要使用异常说明
  • 对于用户以及编译器来说,预先知道函数不会抛出异常有助于简化调用该函数的代码。
  • 如果编译器确认该函数不会抛出异常,就能执行某些特殊的优化操作,而这些优化操作不适用于可能出错的代码
二、noexcept的书写格式
  • 在普通函数函数的参数列表后加上关键字noexcept,告诉编译器和用户改函数不会抛出异常
void fun()noexcept; //该函数不会抛出异常
void fun();         //该函数可能会抛出异常
  • 函数的声明和定义都加上关键字noexcept
  • 可以在函数指针的声明和定义中指定noexcept
  • throw异常说明应该出现在函数的尾指返回类型之前
  • 在typedef或类型别名中不能出现noexcept
  • 类成员函数中,应该出现在const以及引用限定符之后,而在final、override、虚函数=0之前
三、违反异常说明
  • 概念:编译器在编译时并不会检查函数是否有noexcept说明。如果一个函数定义了关键字noexcept,但是该函数在运行时仍然可以抛出异常或者调用可能抛出异常的其它函数
//尽管该函数显式地调用了noexcept,但是该函数仍然可以编译通过
void f()noexcept
{
	throw exception(); //违反了noexcept的异常说明
}
  • 因此:
    • noexcept只是用来说明函数不会抛出异常,但是函数是否会抛出异常与noexcept无关
    • 如果函数抛出了异常,但是程序没有对异常进行处理,则程序就会调用terminate中断程序
四、与throw的兼容性
  • 早期的C++标准设计了throw异常说明,用来在函数后面指定函数可能抛出的异常类型
  • C++11标准的noexcept用来说明函数不会抛出异常(不是强制的)
  • 在下面的语句格式下,throw和noexcept异常说明是等价的
//下面两者具有相同的作用,都是声明函数不会出异常
void fun()noexcept;
void fun()throw();
五、异常说明的实参
  • 概念:
    • noexcept说明符接受一个可选的实参,该实参必须为bool类型
    • 如果实参是true,代表函数不会抛出异常。如果实参是false,代表函数可能会抛出异常
  • 格式如下:
void recoup()noexcept(true); //该函数不会抛出异常
void alloc()noexcept(false); //该函数是否会抛出异常不确定
六、noexcept运算符
  • 功能:
    • noexcept是一个一元运算符。类似于sizeof运算符
    • 返回一个bool值,用于表示给定参数的表达式是否会抛出异常。
    • 如果参数不抛出异常,返回true。否则为false
  • 使用格式:
    • 下面的fun函数不会抛出异常,所以返回true
void fun()noexcept
{}
void main()
{
    cout << noexcept(fun())<< endl;//打印true
}
  • noexcept一个小功能:可以将两个函数的异常说明规定为相同的格式
void fun()noexcept(noexcept(gun()))//gun函数与fun函数的异常说明一致
七、noexcept异常说明与指针
  • 尽管noexcept不属于函数类型的一部分,但是仍影响函数的使用。规则如下:
    • 规则1:如果我们为某个函数指针做了不抛出异常的说明,则该指针只能指向不抛出异常的函数
    • 规则2:相反,如果我们显示或隐式说明了指针可能会抛出异常,则该指针可以指向任何函数,即使承诺不会抛出异常的函数也可以
void recoup()noexcept(true); //该函数不会抛出异常
void alloc()noexcept(false); //该函数可能会抛出异常

void (*pf1)(int) noexcept=recoup;  //正确,pf1与recoup都不会抛出异常
void (*pf2)(int) =recoup;          //正确,recoup不会抛出异常,pf2可能抛出异常,两者互不干扰

pf1 = alloc; //错误,alloc可能抛出异常,但是pf1已经说明了它不会抛出异常
pf2 = alloc; //正确,pf2和alloc都可能抛出异常
八、noexcept异常说明与虚函数
  • 规则如下:
    • 规则1:如果一个虚函数承诺它一定不会抛出异常,则后续派生出来的虚函数也必须做出相同的承诺
    • 规则2:反之,如果基类的虚函数允许抛出异常,则派生类的对应函数允许抛出异常,也可以不允许抛出异常
class A
{
public:
    virtual double f1(double)noexcept; //不会抛出异常
    virtual int f2()noexcept(false);   //可能会抛出异常
    virtual void f3();                 //抛出异常
};

class B :public A
{
public:
    double f1(double);      //错误,Base::f1承若不会抛出异常
    int f2()noexcept(false);//正确,与Base::f2的异常说明一致
    void f3()noexcept;      //正确,Derived的f3做了更严格的限定,是允许的
};
九、noexcept异常说明与拷贝控制
  • 当编译器合成拷贝控制成员时,同时也生成一个异常说明。规则如下:
    • 如果对所有成员和基类的所有操作都承诺了不会抛出异常,则合成的成员是noexcept的
    • 如果合成成员调用的任意一个函数可能抛出异常,则合成的成员是noexcept(false)
  • 如果我们定义一个析构函数但没有为它提供异常说明,则编译器将合成一个。合成的异常说明将与假设由编译器为类合成析构函数时所得的异常说明一致