假设我有以下功能:
// Precondition: foo is '0' or 'MAGIC_NUMBER_4711'
// Returns: -1 if foo is '0'
// 1 if foo is 'MAGIC_NUMBER_4711'
int transmogrify(int foo) {
if (foo == 0) {
return -1;
} else if (foo == MAGIC_NUMBER_4711) {
return 1;
}
}
编译器抱怨“缺少return语句”,但我知道foo
永远不会有0
或MAGIC_NUMBER_4711
不同的值,否则我的函数将没有定义的语义。
这有什么好的解决方案? 这真的是一个问题,即标准说的是什么?
答案 0 :(得分:9)
有时,您的编译器无法推断出您的函数实际上没有没有缺少返回。在这种情况下,存在几种解决方案:
假设以下简化代码(虽然现代编译器会看到没有路径泄漏,只是示例性的):
if (foo == 0) {
return bar;
} else {
return frob;
}
if (foo == 0) {
return bar;
}
return frob;
如果您可以将if语句解释为一种防火墙或前提条件,那么这种方法很有用。
if (foo == 0) {
return bar;
} else {
return frob;
}
abort(); return -1; // unreachable
相应地返回其他内容。评论告诉其他程序员和你自己为什么会这样。
#include <stdexcept>
if (foo == 0) {
return bar;
} else {
return frob;
}
throw std::runtime_error ("impossible");
有些人会回到one-return-per-function a.k.a. single-function-exit-point作为解决方法。这可能在C ++中被视为过时,因为您几乎不知道函数将在何处真正退出:
void foo(int&);
int bar () {
int ret = -1;
foo (ret);
return ret;
}
看起来不错,看起来像SFEP,但逆向工程第三方专有libfoo
显示:
void foo (int &) {
if (rand()%2) throw ":P";
}
如果bar()
为nothrow
,则此参数不成立,因此只能调用nothrow
个函数。
每个可变变量都会增加代码的复杂性,并给代码维护者的大脑容量带来更大的负担。它意味着更多的代码和更多的状态来测试和验证,反过来意味着你从维护者的大脑中吸取更多的状态,反过来意味着维护者的大脑容量减少了重要的东西。
有些类没有默认构造,如果可能的话,你必须编写真正的虚假代码:
File mogrify() {
File f ("/dev/random"); // need bogus init because it requires readable stream
...
}
只是为了宣布它,这是一个非常黑客。
答案 1 :(得分:2)
在C89和C99中,永远不需要return语句。即使它是一个返回值不同于void
的函数。
C99只说:
(C99,6.9.1p12“如果达到了终止函数的},并且调用者使用了函数调用的值,则行为是未定义的。”
在C ++ 11中,标准说:
(C ++ 11,6.6.3p2)“在函数末尾流动等效于没有值的返回;这会导致值返回函数中的未定义行为”
答案 2 :(得分:2)
只是因为你可以告诉输入只有两个值中的一个并不意味着编译器可以,所以预计它会产生这样的警告。
您可以通过几种方法帮助编译器解决这个问题。
您可以使用枚举类型,其中两个值是唯一有效的枚举值。然后编译器可以立即告诉两个分支中的一个必须执行,并且没有丢失的返回。
您可以在功能结束时abort
。
您可以在功能结束时throw
使用适当的例外。
请注意,后两个选项优于静音警告,因为它可预测地显示违反前提条件的时间,而不是允许未定义的行为。由于该函数采用int
而不是类或枚举类型,因此有人使用除两个允许值之外的值调用它并且您希望在开发周期的早期捕获它们只是时间问题。可能而不是将它们作为未定义的行为推迟,因为它违反了函数的要求。
答案 3 :(得分:0)
实际上编译器正在做它应该做的事情。
int transmogrify(int foo) {
if (foo == 0) {
return -1;
} else if (foo == MAGIC_NUMBER_4711) {
return 1;
}
// you know you shouldn't get here, but the compiler has
// NO WAY of knowing that. In addition, you are putting
// great potential for the caller to create a nice bug.
// Why don't you catch the error using an ELSE clause?
else {
error( "transmorgify had invalid value %d", foo ) ;
return 0 ;
}
}