有趣的答案:尽管到目前为止,我都同意所有这些观点,但是到目前为止,这个问题可能有一些涵义,这些涵义现在已经被完全忽略了。
如果上面的简单示例扩展了资源分配,然后进行了错误检查并可能释放资源,那么情况可能会发生变化。
考虑初学者可能采取的幼稚方法:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
上面的代码代表了过早返回的风格的极端版本。请注意,随着复杂性的增加,代码会随着时间变得非常重复且不可维护。如今,人们可能会使用异常处理来捕获这些异常。
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
在查看下面的goto示例之后,Philip建议在上面的catch块内使用一个不间断的开关/外壳。可以先切换(typeof(e))然后掉入free_resourcex()
调用,但这并不容易,需要进行设计考虑。请记住,没有中断的开关/外壳与下面带有菊花链标签的goto完全一样...
正如Mark B所指出的那样,在C ++中遵循资源获取即初始化原则(简称RAII)被认为是一种很好的风格。该概念的要点是使用对象实例化来获取资源。一旦对象超出范围并调用其析构函数,资源便会自动释放。对于相互依赖的资源,必须格外小心,以确保正确的解除分配顺序并设计对象类型,以使所需的数据可用于所有析构函数。
或者在例外前几天可能会做:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
但是,这个过于简化的示例有几个缺点:仅当分配的资源彼此不依赖时才可以使用它(例如,它不能用于分配内存,然后打开文件句柄,然后将数据从句柄读取到内存中),并且不提供单独的,可区分的错误代码作为返回值。
为了使代码保持快速(!),紧凑,易于阅读和可扩展的Linus Torvalds为内核代码实施了与资源相关的另一种样式,甚至以绝对合理的方式使用了臭名昭著的goto:
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
关于内核邮件列表的讨论的要点是,大多数在goto语句上“首选”的语言功能都是隐式的gotos,例如巨大的树状if / else,异常处理程序,loop / break / continue语句等。在上面的示例中,goto被认为是可以的,因为它们仅跳跃了一小段距离,具有清晰的标签,并且释放了其他混乱的代码来跟踪错误情况。这个问题也在stackoverflow上进行了讨论。
但是,最后一个示例中缺少的是一种返回错误代码的好方法。我当时想result_code++
在每次free_resource_x()
调用后添加一个,然后返回该代码,但这抵消了上述编码样式的一些速度提升。如果成功,则很难返回0。也许我只是没有想象力;-)
所以,是的,我确实认为在编码过早返回与否之间存在很大的差异。但是我也认为,只有在更复杂或难以为编译器进行重构和优化的更复杂的代码中,这种情况才显而易见。一旦资源分配起作用,通常就是这种情况。