[英]Can this use of C++ exceptions justified
我有一个C ++ API,它会在错误情况下引发异常。 通常,我在C ++ API中看到的用于通知错误的方法是通过特殊的返回码和函数返回的最后一个错误字符串,如果方法返回错误代码,则可以检查该错误字符串。 此方法有局限性,例如,如果您需要从函数返回整数,并且整个整数值范围对于返回值均有效,那么您就不能返回错误代码。
因此,当函数中发生错误时,我选择在API中引发异常。
这是C ++中异常的可接受用法吗?
另外,在我的API的某些函数中(例如authenticate()),我有两个选择。
如果使用第一个选项,则它与其他函数不一致,因为它们都会抛出异常。 另外,很难指出什么是错误。
那么在这样的功能中也可以使用第二种方法吗?
在下面的答案中,提到使用C ++异常控制程序流是不好的。 另外,我在其他地方也听到过同样的声音。
https://stackoverflow.com/a/1736162/1015678
我的用法违反吗? 我无法在此处明确标识我是否在使用异常来控制程序流。
我在C ++ API中看到的用于通知错误的方法是通过特殊的返回码和函数返回的最后一个错误字符串,如果方法返回错误代码,可以检查该错误字符串。
有时候这样做是有充分的理由的,但是当您看到C ++库包装了一个较旧的C库,由更熟悉C的人编写,为更熟悉C的客户编码员编写,或者为与C互操作而编写时,这种情况会更多。
从函数返回整数,并且整个整数值范围对于返回值均有效,因此您无法返回错误代码。
选项包括:
例外情况
以更广泛的类型返回(例如getc()
返回带有-1
的int
,表示EOF)。
返回值旁边的成功标志,包装在boost::optional
, pair
, tuple
或struct
具有成功标志中的至少一个和/或由呼叫方拥有和指定给该函数作为非数值const
按引用或指针参数
这是C ++中异常的可接受用法吗?
听起来还不错,但是目前的技术是权衡利弊,我们不知道对于客户端代码调用您的函数而言,它是否最方便,最可靠。 了解他们对密钥的期望,这部分地基于他们的整体C ++经验,而且还可以从您的API的其余部分以及与您一起提供的任何其他API中获得,甚至可以从他们可能在其中使用的其他库的其他API中获得。相同的应用程序等。
还要考虑函数的调用者是否可能希望在调用的上下文中与其他可能的失败分开处理该函数的成功或失败。 例如,有时客户端代码更容易使用返回布尔成功值的函数:
if (fn(1) && !fn(2))
fn(3);
try
{
fn(1);
try
{
fn2();
}
catch (const ExpectedExceptionFromFn2Type&)
{
fn3();
}
}
catch (const PossibleExceptionFromFn1Type&)
{
// that's ok - we don't care...
}
但有时例外会更容易:
try
{
My_X x { get_X(99) };
if (x.is_happy(42))
x += next_prime_after(x.to_int() * 3);
}
catch (std::exception& e)
{
std::cerr << "oops\n";
}
...相比...
bool success;
My_X x;
if (get_X(&x, 99)) {
if (x.is_valid() {
bool happy;
if (x.can_get_happy(&happy, 42) && happy) {
int my_int;
if (x.can_convert_to_int(&my_int)) {
if (!x.add(next_prime_after(x.to_int() * 3))) {
std::cerr << "blah blah\n";
return false;
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
} else { cerr / return false; }
(它的严重程度取决于函数是否支持报告错误,或者可以信任该函数始终运行。这也很困难,因为发生的事情使函数有可能开始失败(例如,它开始使用一个可能会丢失),如果客户端代码尚未接受并检查错误代码或捕获异常,则一旦识别出潜在的错误,可能需要对客户端代码进行重新处理。 “很幸运-可能会传播到一些已经适当的客户端catch语句,但是另一方面,如果这样假设至少没有盯着客户端代码,这是有风险的。)
一旦考虑了对客户端使用情况的了解,仍然可能会怀疑哪种方法最好:通常您可以在整个API中选择并坚持使用,但是有时您可能希望提供函数的多个版本,例如:
bool can_authenticate();
void authenticate_or_throw();
...要么...
enum Errors_By { Return_Value, Exception };
bool authenticate(Errors_By e) { ... if (e == Exception) throw ...; return ...; }
...要么...
template <class Error_Policy>
struct System
{
bool authenticate() { ... Error_Policy::return_or_throw(...); }
...
}
另外,在我的API的某些函数中(例如
authenticate()
),我有两个选择。
如上所述,您有两个以上的选择。 无论如何,一致性非常重要。 听起来异常是适当的。
提到使用C ++异常控制程序流是不好的
这正是异常的作用以及所有异常可以被使用的原因,但是我确实理解您的意思。 最终,要达到适当的平衡是一门艺术,它使用了许多其他软件,同时考虑到您的客户将与您的库一起使用的其他库等。也就是说,如果某种意义上的“错误”,至少是合理考虑例外情况。
对于诸如authenticate()
类的东西,如果您能够为认证计算出true / false值,那么我希望您返回布尔值;如果由于某种原因而无法执行此操作,则抛出异常。 关于使用异常进行流控制的评论建议不要做类似的事情:
try {
...
authenticate();
// rely on the exception to not execute the rest of the code.
...
} catch (...) { ... }
例如,我可以想象一个authenticate()
方法依赖于联系某个服务,并且如果由于某种原因而无法与该服务进行通信,则您将不知道凭据的好坏。
再说一次,API的另一个主要经验法则是“保持一致”。 如果API的其余部分在类似情况下依赖于异常充当false值,请使用该值,但对我来说,这有点丑陋。 对于特殊情况,我倾向于保留异常-即,罕见,在正常操作情况下永远都不会发生。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.