优秀的编程知识分享平台

网站首页 > 技术文章 正文

C++之异常处理机制

nanyue 2025-01-20 15:36:33 技术文章 3 ℃
  1. C++异常处理基本语法
  2. noexcept 修饰符和noexcept 运算符
  3. 异常修饰符与指针、虚函数
  4. C++标准异常类

本文重点讲C++异常机制的语法和使用,关于C++异常机制的实现原理,敬请期待。

  1. C++异常处理基本语法

通过 throw 和 try...catch 语句实现异常处理。throw 语句的语法如下:

throw  表达式;

意思是抛出异常,表达式代表一个异常,其值的类型可以是基本类型,也可以是类。

try...catch 语句的语法如下:

try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
catch(异常类型) {
    异常处理代码
}

catch 可以有多个,但至少要有一个。

try...catch 语句的执行过程是:

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,即所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。
  • 如果当前try…catch…块内找不到匹配该异常对象的catch语句,则由更外层的try…catch…块来处理该异常;层层递归回退,直到退到主函数main()都不能处理该异常,则调用terminate()终止程序

如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:

catch(...) {
 ...
}

2. noexcept修饰符 和 noexcept运算符

noexcept用来告诉编码者或编译器,函数是否会抛出异常为什么要告诉编码者或编译器呢?首先,知道函数不会抛出异常让函数调用者不必再考虑如何处理异常;然后,如果编译器确认函数不会抛出异常,它就能执行某些特殊的优化操作。

关键字noexcept紧跟在函数的参数列表后面,例如:

void throwExcept()  noexcept; 	//承诺函数不会抛出异常。
void throwExcept() ;                   //不确定函数是否会抛出异常。

我将其理解为异常承诺,即承诺不会抛出异常。我们也可以在函数指针的声明和定义中指定noexcept,在typedef或类型别名中则不能出现noexcept。

在成员函数中,noexcept修饰符需要跟在const及引用限定符之后,而在final、override或虚函数的=0之前。

违反异常承诺

大家需要清楚一个事实:编译器不会在编译时检查noexcept说明,如果一个函数在noexcept修饰的同时又含有throw或者调用了可能抛出异常的其它函数,编译器将顺利编译通过,并不会因为这种违反异常承诺的情况而报错。

void  throwExcept()  noexcept//承诺不会抛出异常
{
      throw std::exception();			//抛出异常
}

一旦一个noexcept函数抛出了异常,程序就会调用std::terminate()以确保遵守程序在运行时不会抛出异常的承诺。

异常说明的实参

noexcept修饰符接受一个可选的实参,类型为bool:如果为true,则表示函数不会抛出异常;如果为false,则表示不确定函数是否会抛出异常。

void throwExcept()  noexcept(false)  ;
void throwExcept()  noexcept(true)   ;		// noexcept(true) 相当于noexcept
void throwExcept() ;															 //隐式	noexcept(false)

noexcept运算符

noexcept运算符是一个一元运算符,返回值是bool,用于表示给定的表达式是否会抛出异常,如下面的例子:

void throwExcept() ;
void unknownThrow()   noexcept(noexcept(throwExcept()))  ;
//第一个noexcept是修饰符,第二个noexcept是运算符。
//noexcept(throwExcept())表示,如果throwExcept不抛出异常,则结果为true,否则为false。
//即unknownThrow()的异常说明与throwExcept()一致

noexcept作为运算符时,通常可以用于模板:

template<class T>
void fun() noexcept(noexcept(T());	
//fun函数是否是一个noexcept函数,将由T()表达式是否为noexcept所决定。

3. 异常修饰符与指针、虚函数

如果我们为某个指针做了不抛出异常的声明,则该指针将只能指向不抛出异常的函数;返之,如果我们显示或隐式地说明了指针可能抛出异常,则该指针可以指向任何函数。

void throwExcept()  noexcept;
void (*pf1)() noexcept = throwExcept;		//正确,pf1和throwExcept承诺都不会抛出异常
void (*pf2)() = throwExcept;									//正确,pf2可能抛出异常,throwExcept承诺不会抛出异常
void throwExcept();
void (*pf1)() noexcept = throwExcept;		//错误,pf1承诺不抛出异常,throwExcept可能抛出异常
void (*pf2)() = throwExcept;									//正确,pf2可能抛出异常,throwExcept可能抛出异常

如果基类的虚函数承诺了它不会抛出异常,则派生出来的函数也必须做出不会抛出异常的承诺;同样,如果基类的虚函数未做出承诺,则派生出来的函数即可以做出承诺也可以不做承诺。

4. C++标准异常类

bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range、logic_error、runtime_error 都是 exception 类的派生类,使用这些异常类需要包含头文件 <exception>

std::exception

最基本异常,所有异常都继承自它,只报告异常的发生。

std::bad_cast

用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常

std::ios_base::failure

输入/输出库中的函数在失败时抛出的异常对象

std::bad_alloc

在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常

std::bad_typeid

使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常

std::logic_error

程序逻辑错误.

std::out_of_range

程序逻辑错误:用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常

std::length_error

程序逻辑错误:试图创建一个超出该类型最大长度的对象

std::demain_error

程序逻辑错误:参数对应的结果值不存在

std::invalid_argument

程序逻辑错误:无效参数

std::runtime_error

运行时错误.

std::rang_error

运行时错误:产生了超出有意义值域范围的结果

std::overflow_error

运行时错误:计算上溢

std::underflow_error

运行时错误:计算下溢

Tags:

最近发表
标签列表