一、什么是异常
什么是异常?什么是错误?什么是陷阱?这个在学汇编语言时,都有过详细的说明。这里只说异常,异常,就是普通人们理解的不同于人们设想的那样的结果,就是异常。异常是程序没有按照设计而产生的结果警示。这个结果,可能是一个命令行的提示,可能是写一个文件,也可能是直接程序挂掉(有资料把这种情形不归到异常中)。
异常,首先是一种错误,有可能是逻辑上的,比如本来想输出A,结果输出了B;也有可能是控制上的,比如网络连接断开了;也有可能是单纯硬件的,诸如此类。在c++的书籍里,包括一些大牛的推荐里,对异常都不置可否。所以一般程序在代码中,看到异常控制机制的代码一般不会很多,大多还是通过返回值来处理错误的结果。
二、c++的异常处理
c++中的异常处理机制不复杂,表现形式就是:
1) 带参数名称
try { /* */ } catch (const std::exception& e) { /* */ }
2) 不带参数名称
try { /* */ } catch (const std::exception&) { /* */ }
3) 捕获任意异常
try { /* */ } catch (...) { /* */ }
当然,异常是需要在控制的代码段中抛出的:
if (...)
{
throw x;
}
这样,就会在上面的代码中捕获这里throw的结果对象。下面看一个完整的例子:
#include <iostream>
#include <vector>
int main() {
try {
std::cout << "Throwing an integer exception...\n";
throw 42;
} catch (int i) {
std::cout << " the integer exception was caught, with value: " << i << '\n';
}
try {
std::cout << "Creating a vector of size 5... \n";
std::vector<int> v(5);
std::cout << "Accessing the 11th element of the vector...\n";
std::cout << v.at(10); // vector::at() throws std::out_of_range
} catch (const std::exception& e) { // caught by reference to base
std::cout << " a standard exception was caught, with message '"
<< e.what() << "'\n";
}
}
在c++,异常的处理是有顺序的,一般会按照控制的具体情况逐渐向上扩展,即最精确-精确-较精确-模糊-主程序,其实也就是栈处理的顺序,越靠前的越先处理。所以在编写异常控制时,尽量也要照这个顺序来编写控制代码,以方便快速定位到相关的错误点。否则随便搞出一个异常控制了,对研判程序的错误定位没啥帮助,那这个异常控制的目的就没有了。
try {
f();
} catch (const std::overflow_error& e) {
// this executes if f() throws std::overflow_error (same type rule)
} catch (const std::runtime_error& e) {
// this executes if f() throws std::underflow_error (base class rule)
} catch (const std::exception& e) {
// this executes if f() throws std::logic_error (base class rule)
} catch (...) {
// this executes if f() throws std::string or int or any other unrelated type
}
异常可以抛出对象也可能抛出基础类型,需要说明的是,如果是对象,其类必须是可复制的,切记。异常同样可以嵌套,目的和不嵌套没有什么不同。在一个Catch捕获一个异常后,如果觉得没有啥用,可以向外继续抛出这个异常。如果所有的异常都没有被程序自己捕获,最终会调用std::terminate()这个函数来处理。也就是前头提到的那个顺序表述。调用这个函数后,就不要再想费尽心机的想怎么办了。老实挂掉就可以了,虽然有一些小技巧可以使用,基本不推荐了,也就是说,不要再妄想恢复程序了。
三、c++17中对异常的处理
以前在面试中,经常会有人问下面的问题,在构造和析构函数中如果发生异常怎么办?有兴趣可以查查怎么办。但在c++17中,析构函数默认是noexcept,也就是说不会抛出异常。如果知道某个函数不会出现异常,或者干脆不想抛出异常,可以在函数中增加这个关键字:
int MyFunction(int args) noexcept
{
......
}
这个关键字不错,可以在实际工程上搞一搞。在c++17还可以显示动态列出异常:
void f() throw(int); // OK: function declaration
void (*pf)() throw (int); // OK: pointer to function declaration
void g(void pfa() throw(int)); // OK: pointer to function parameter declaration
typedef int (*pf)() throw(int); // Error: typedef declaration
四、标准库的异常
如同所有的语言一样,c++在标准库中也封装了std::exception这个异常类,其它有不少都继承这个类,如下图:

有了标准的异常,就可以直接使用,如果不太符合自己的应用,还可以继承它,在相关的函数中完善并再次应用:
class MyException:public std::exception
{
public:
MyException(std::string_view sv = "my test exception!")
private:
std::string s_ = "";
};
实践出真知!
五、总结
总之,在C/C++中,不推荐使用异常控制机制,这话可是大牛们说的。有一些异常,比如指针越界,只有等到释放内存时才会崩溃,异常根本也控制不住。所以在c++中,异常有点鸡肋的感觉。还不如学C,用好返回值做处理。c++中还有一个特点,一旦产生异常,大概率下整个线程甚至进程就挂了。所以对这些异常的处理,往往也起不到其它语言中的作用。这也是c++难的一个原因.
另外一个问题是,异常控制住,那么异常带来的资源管理怎么办?该不该释放还是要继续保持,这需要好好的根据上下文斟酌。c++中的RAII机制,就是一个不错的选择,包括智能指针,但程序毕竟是人写的,还是要小心。
好好把代码写好,才是王道。


本文深入探讨了C++中的异常处理,包括异常的定义、C++异常处理机制的使用,如try-catch块,以及C++17的新特性。强调了在构造和析构函数中异常处理的变化,并介绍了标准库中的异常类。同时,讨论了异常处理的顺序和资源管理问题,以及如何自定义异常类。最后,提出了C++中异常处理的争议和返回值错误处理的建议。
2万+

被折叠的 条评论
为什么被折叠?



