我想知道我是否可以在C:中以以下方式将错误代码返回为double
double getValue()
{
double ret = 0;
if (error1)
{
return -1;
}
if (error2)
{
return -2;
}
return ret = ....;
}
int main(void)
{
double val = getValue();
if (-1 == val)
{
printf("getValue: error1n")
return -1;
}
else if (-2 == val)
{
printf("getValue: error2n");
return -2;
}
......
return 0;
}
所以当返回值>=0时,它是可以用于计算的正确值。当值小于零时,出现错误。当我将返回值与-1或-2进行比较时,我会遇到浮点比较问题吗?
标志值是个坏主意。即使是双倍精度,浮点标志值也是双倍精度。
如果使用IEEE双精度浮点值,则值-1
和-2
可以精确地表示为double
s,并且比较定义良好。如果您只复制double
或只读取值,就不会出现"神奇的错误"。事实上,在具有传统2s补码32位int
s的系统上,每个int
都可以精确地表示为IEEE双精度浮点值。
现在,你认为像x /3. * 3.
这样无关紧要的转换会破坏身份,所以代码是非常脆弱的:脆弱既是因为标志值是脆弱的,也是因为浮点等价在实践中往往是脆弱的。
在C++中,有无数种不那么脆弱的方法可以做到这一点。
enum error_code {a,b,c};
boost::variant<double, error_code> getValue();
是可以容纳double
或error_code
的标记并集。你可以看到一个std::expected
提案,它是一个标记的并集,对第一个值是唯一有效的值有"偏见"(有点像std::experimental::optional
和boost::variant
之间的交叉)。
这两种情况都会导致以类型安全的方式返回值,其中错误是与非错误返回类型不同的类型值。
替代的解决方案包括单独返回错误代码(作为返回值,或者将指向错误代码的指针作为参数(我称之为ICU样式))。在这种情况下,double
可以设置为一些无害的值(比如NaN
),而不是保持未初始化状态。
double getValue( error_code* e );
或
error_code getValue( double* out );
其中CCD_ 15是错误代码的枚举。
@LightnessRacesinOrbit击败了我,但在输入后,我还是发布了它。
您可以通过将要设置的值作为指针参数并返回状态来完成此操作。这样,就不会禁止*ret
的值。
int getValue(double *ret)
{
*ret = ...;
if (error1)
return -1;
if (error2)
return -2;
return 0;
}
然后调用代码可以是
double myval;
int err;
if ((err = getValue(&myval)) == 0)
printf ("getValue() returned %fn", myval);
else
printf ("getValue() returned error %dn", err);
是的,您可能会得到浮点错误。
因此,考虑使用异常,或者可能返回int
错误代码,并在成功时填充double
"out参数":
int getValue(double& ret)
{
if (error1)
return -1;
if (error2)
return -2;
ret = ....;
return 0;
}
这样做是不必要的,并且会使错误处理变得困难,您应该创建一个enum
,在那里您可以根据需要添加或删除错误代码,而且您也不需要真正记住-1
是什么或-2
的意思,只需为每个错误提供一个描述性名称,并执行此
enum ErrorCodes {NoError, Error1, Error2, ... , ErrorN};
enum ErrorCodes getValue(double *value)
{
if (error1)
return Error1;
if (error2)
return Error2;
.
.
.
if (errorN)
return ErrorN;
*value = resultOfCalculation;
return NoError;
}
然后
enum ErrorCode code;
double value;
code = getValue(&value);
switch (code)
{
case NoError:
/* use value here */
break;
case Error1:
/* handle error 1 */
break;
/* and so on */
}
我认为这是更好、更优雅的,因为您可以在任何时候应用它来进行稳健的错误检查,无论目标值是什么类型,这对struct
、double
或int
阵列都是完全一样的。