在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
用C++写代码的时候总是避免不了处理错误,一般来说有两种方式,通过函数的返回值或者抛出异常。C语言的错误处理一律是通过函数的返回值来判断的,一般是返回 C++的错误处理方式C++号称向下兼容C语言,于是就将C语言通过返回值的错误处理方式也搬了进来。但C++最大的不同是引入了异常机制,可以用 异常的优点
异常的缺点
返回值的优点
返回值的缺点
使用异常还是返回值我的观点是,用异常来表示真正的、而且不太可能发生的错误。所谓不太可能发生的错误,指的是真正难以预料,但发生了却又不得不单独处理的,譬如内存耗尽、读文件发生故障。而在一个字符串中查找一个子串,如果没有找到显然应该是用一个特殊的返回值(如 一句话来概况就是不要用异常代替正常的控制流,只有当程序真的「不正常」的时候,才使用异常。反过来说,当程序真正发生错误了,一定要使用异常而不是返回一个错误代码,因为错误代码总是倾向于被忽略。如果要保证一个以返回值来表示错误代码的函数的错误正确地向上传递,需要在每个调用了可能产生错误的函数后面都判断一下是否发生了错误,一旦发生了不可解决的错误,就要终止当前函数(并释放当前函数申请的资源),然后向上传递错误。这样一来错误处理代码会被重复地写好几遍,十分冗杂,譬如下面代码: int func(int n) { int fd = open("path/to/file", O_RDONLY); if (fd == -1) { return ERROR_OPEN; } int* array = new[n]; int err; err = do_something(fd, array); if (err != SUCCESS) { delete[] array; return err; } err = do_other_thing(); if (err != SUCCESS) { delete[] array; return err; } err = do_more_thing(); if (err != SUCCESS) { delete[] array; return err; } delete[] array; return SUCCESS; } 对使用异常容易增加函数出口的指控其实是不成立的,因为即使使用返回值,这些出口也是免不了的,除非程序员有意或无意忽略掉,但异常是不可忽略的。如果你认为可以把判断错误的 有些错误几乎总是可以被立即恢复(譬如前面所说的查找一个字符串不存在的子串,甚至都不能说这是一个「错误」),而且返回值本身就传递一定信息,就不需要使用异常了。 鉴于C++没有统一的ABI,并不建议在模块的接口上使用异常。如果要使用,就要把可能曝露给用户的异常全部声明出来,不要把其他类型的异常丢给用户去处理,尤其是内部状态——模块的使用者通常也不会关心模块内部具体是哪条语句发生错误了。 构造函数中的错误有一个相当实际的问题是,如何处理构造函数的错误?我们都知道构造函数是没有返回值的,怎么办呢?通常有三种常见的处理方法,标记错误状态、使用一个额外的 合格的C++程序员都知道C++的析构函数中不应该抛出异常,一旦析构函数中的异常没有被捕获,整个程序都要被中止掉。于是许多人就对在构造函数中抛出异常也产生了对等的恐惧,宁可使用一个额外的初始化函数在里面初始化对象的状态并抛出异常(或者返回错误代码)。这样做违背了对象产生和初始化要在一起的原则,强迫用户记住调用一个额外的初始化函数,一旦没有调用直接使用了其他函数,其行为很可能是未定义的。 使用初始化函数的惟一好处可能是避免了手动释放资源(释放资源的操作交给析构函数来做),因为C++的一个特点是构造函数抛出异常以后析构函数是不会被调用的,所以如果你在构造函数里面申请了内存或者打开了资源,需要在异常产生时关闭。但想想看其实并不能完全避免,因为有些资源可能是要在可能产生错误的函数调用过后才被申请的,还是无法完全避免手工的释放。 标记错误状态也是一种常见的形式,譬如STL中的 最直接的方法还是在构造函数中抛出异常,它并不会向析构函数中抛出异常那样有严重的后果,只是需要注意的是抛出异常以后对象没有被创建成功,析构函数也不会被调用,所以应该自行把申请的资源全部都释放掉。 如何在构造函数中捕获异常构造函数与普通函数有一个很不一样特性,就是构造函数可以有初始化列表,例如下面的代码: class B { public: B(int val) : val_(val * val) { } private: int val_; }; class A { public: A(int val) : b_(val) { a_ = val; } private: int a_; B b_; }; 以上的代码中 class B { public: B(int val) : val_(val * val) { throw runtime_error("wtf from B"); } private: int val_; }; class A { public: A(int val) try : b_(val) { a_ = val; } catch (runtime_error& e) { cerr << e.what() << endl; throw runtime_error("wtf from A"); } private: int a_; B b_; }; 注意上面 class A { public: A(int val) try : b_(val) { a_ = val; } catch (runtime_error& e) { cerr << e.what() << endl; } private: int a_; B b_; }; 这种语法是C++的标准,而且目前已经被所有的主流C++编译器支持(VS2010、g++ 4.2、clang 3.1),所以几乎不存在兼容性问题,大可放心使用。 其他语言中的错误处理Java倾向于大量使用异常,而且还把异常分为了两类分别是检查型异常(Checked Exception)和非检查型异常(Unchecked Exception),检查型异常就是 Python和Java一样也倾向于使用异常,并不一定真的发生故障才抛出异常,譬如字符串转换为整数,如果字符串不合法,Python会抛出一个 相较于Java和Python,Go的错误处理是另一个极端,Go语言则根本没有异常的概念,而是普遍采用返回值的方式来表示错误,同时还提供了 Go语言的 Sub ErrorDemo On Error GoTo ErrorHandler Dim a as Integer a = 1/0 ' An error occurs. Print a ' Go back here Exit Sub ErrorHandler: ' Code that handles errors. Resume End Sub Visual Basic中还有 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论