返回信息流1、检测返回值
2、assert宏
3、异常catch
4、...
LZ对这几种方式好疑惑,,望大神帮缕一缕
这是一条镜像帖。来源:北邮人论坛 / cpp / #92825同步于 2016/7/27
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
函数错误的处理方式,你倾向于哪一种
BaiWfg2
2016/7/27镜像同步5 回复
订阅后,新回复会通过你的通知中心匿名送达。
5 条回复
都有在用
一般网络通信的函数,执行之后会有返回值,包含错误信息,理所当然的检测返回值了
一些函数传入的实参做检测,指针为空,句柄为空,这时候用ASSERT()
try catch在检测的是系统异常……因为有前两种,感觉C++用的不是很多,JAVA用的比较多
assert不是错误处理方式。一旦assert失败,程序就死了,没有处理的机会。
检查错误的方式一般有3种:
1. 返回值
第一种用一种特殊的返回值表示错误信息。比如,某个线性查找函数返回找到的元素的下标,但如果没找到,就返回-1这样的特殊值。本应该返回指针的函数可以返回NULL。有的编程语言,比如Haskell、Scala、Rust或者Java1.8,C++也行,提供Option<T>类型,可以返回None或者Some(T)两种值,前者用于异常情形。相当于一种更强类型的“可以是NULL的值”。
2. 全局(或threadlocal)变量
有的语言(尤其是C)会在发生异常的时候,函数设置某个全局变量的值,然后返回一个没有意义的值。调用者调用完要探测这个全局变量的值,来确定是否发生了错误。最常见的就是errno。大多数POSIX函数(比如open),失败的时候返回0或者-1之类的特殊值,同时把全局变量errno设置为错误码。所以C语言里经常能看到:
int result = open(blahblah);
if (result == -1) {
fprintf(stderr, "ERROR: %s", stderror(errno));
exit(1);
}
但这显然有问题:多线程的程序,errno如果真是全局变量,就会被别的线程覆盖,使得一个线程就算没有发生错误也会发现errno被设置了。C语言是在多线程还是“高级概念”的远古时期发明的,就连C99标准里对“线程”也只字不提。从C11开始,errno成了thread-local的变量:每个线程有自己的errno。
3. 抛异常
C++/Java/Python/Scala拥有try-catch结构,允许异常从一个函数直接跨过多个函数,传播到可以捕获异常的地方(catch)。如果没有发生异常,程序正常返回,什么也不发生。
三种方法各有好坏。
第1种方法,在错误频繁出现的时候,效率最高。但是,坏处相应的就是:不管出错没有出错,调用者都要去检查返回值是不是特殊值。所以,这种做法的代价就是使得常见的路径(fast path)变慢。
第3种方法,在错误很少出现的时候,效率最高,因为编译器可以把catch部分放到正常的控制流之外,也就是说如果没有出错,程序的效率就像try-catch根本不存在一样。当然,相应的,坏处就是:一旦发生了异常,程序要做stack unwinding,也就是不断地去除栈顶的帧(同时恢复callee-saved寄存器),直到找到能处理异常的函数,然后跳到相应的代码位置去,这整个过程很复杂,比“正常返回”慢得多。所以,如果错误频繁出现,这种方式就不太好了。
第2种方法很少有人用,据我所知,只有C语言的POSIX API函数会这样使用errno。官方的Python解释器(CPython)是用C语言写的解释器,而C语言没有try-catch,所以也用一个全局变量纪录“当前函数里Python抛的异常”然后“正常地”从Python函数里返回,然后调用者检查这个标记是否设置了。这种滥用也是Python的GIL锁难移去除的原因之一,尽管后来的Python版本开始把这个全局异常变量改成thread-local的了。其他的语言(包括Python语言本身,我是说语言层面,不是实现层面)不是用方法1就是用方法3。
用一个例子最能说明问题了:有一个字典,和1000000个键(可能少量重复),现在有两个任务:
1. 判断字典是否已经包含这个键。如果没有,就插入;如果有,打印出当前键的值以及以前重复的那个键的值。
2. 不断查找。已知99.9%以上的键是存在的。如果不存在,打印错误信息并继续。
第一个任务,因为绝大多数的键是不存在的,如果查询的时候“找不到”会跑出异常,那么就太慢了。所以,最好用返回特殊值的方法查询。
第二个任务,因为我们已知99.9%以上的键是存在的,所以不值得每次都判断错误,而是仅在出错的时候走异常控制流。
【 在 xiaobing307 的大作中提到: 】
: http://www.zhihu.com/question/23669218
这个帖子里的例子是参数的合法性验证。C/C++语言里memcpy,如果src或者dst是nullptr,那么后果是“未定义行为”,就是什么都可以发生,从什么都不发生到机器冒烟都可以的。所以断言是可以的(也是最好的:立即报错然后死)。
Java里,API一般在参数出错的时候抛IllegalArgumentException。所以,一般就是if(...) throw ...了。Scala里有一个require函数,用法和assert类似,但是一旦失败就抛IllegalArgumentException。