我有一个C ++ API,可以在错误条件下抛出异常。通常,我在C ++ API中看到的用于通知错误的方法是通过特殊的返回代码和函数来返回最后一个错误字符串,如果方法返回错误代码,则可以检查该字符串。此方法有一些限制,例如,如果您需要从函数返回一个整数,并且整个整数值范围对返回值有效,那么您将无法返回错误代码。
由于这个原因,我选择在函数中发生错误时在我的API中抛出异常。
这是C ++中异常的可接受用法吗?
此外,在我的API中的一些函数中(例如authenticate()),我有两个选项。
如果使用第一个选项,则它与其他函数不一致,因为它们都会抛出异常。此外,很难指出错误是什么。
在这样的函数中也可以使用第二种方法吗?
在下面的回答中,提到使用C ++异常来控制程序流是不好的。另外,我也在其他地方也听到了同样的声音。
https://stackoverflow.com/a/1736162/1015678
我的使用是否违反此规定?我无法清楚地确定我是否在此处使用例外来控制程序流程。
答案 0 :(得分:4)
我在C ++ API中看到的通知错误的方法是通过特殊的返回代码和函数返回最后一个错误字符串,如果方法返回错误代码,可以检查它。
有时候这样做是有充分理由的,但更常见的是,当你看到C ++库包装了一个较旧的C库时,已经被更熟悉C的人编写,为客户编码人员编写更适合C的人,或者是为了与C的互操作性而编写的。
从函数返回一个整数,整个整数值范围对返回值有效,因此您无法返回错误代码。
选项包括:
例外
使用更宽的类型返回(例如getc()
会返回int
,-1
表示EOF)。
在值旁边返回成功标记,包含在boost::optional
,pair
,tuple
或struct
让调用者拥有至少一个成功标志和/或值,并将该函数指定为非const
by-reference或by-pointer参数
这是C ++中异常的可接受用法吗?
听起来不错,但艺术是在平衡利弊,我们不知道它是否为客户端代码调用您的功能提供了最佳的方便性和可靠性。了解他们对密钥的期望,部分是基于他们的整体C ++经验,以及其他API以及与您一起提供的任何其他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; }
(它究竟有多糟糕取决于函数是否支持报告错误,或者可以信任它始终有效。这也很困难,因为它发生的事情使得函数可能开始失败(例如如果客户端代码尚未接受并检查错误代码或捕获异常,则会开始使用可能丢失的数据文件,然后一旦识别出错误的可能性,可能需要重新编写客户端代码。对于例外来说不那么真实 - 当你很幸运时 - 可能会传播到一些已经合适的客户捕获语句,但另一方面,如果没有至少关注客户端代码,这是冒险的。)
一旦你考虑过你对客户使用情况的了解,可能仍然会对哪种方法最好是有疑问的:通常你可以选择一些东西并在整个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 ++异常来控制程序流程是不好的
这正是异常所做的以及它们可以用于的所有内容,但我明白你的意思。最终,取得适当的平衡是一种艺术,它使用了很多其他软件,考虑到你的客户将与你的其他库一起使用的其他图书馆。那就是说,如果某事是错误的&#34;从某种意义上说,考虑例外情况至少是合理的。
答案 1 :(得分:1)
对于类似authenticate()
的内容,如果您能够为身份验证计算真/假值,我希望您返回bool,如果某些内容阻止您这样做,则抛出异常。关于使用流量控制异常的评论建议 NOT 执行以下操作:
try {
...
authenticate();
// rely on the exception to not execute the rest of the code.
...
} catch (...) { ... }
例如,我可以想象authenticate()
方法依赖于联系某些服务,如果由于某种原因无法与该服务进行通信,则您不知道凭据是否为好还是坏。
然后,API的另一个主要经验法则是&#34;是一致的&#34;。如果API的其余部分依赖异常作为类似情况下的假值,请使用它,但对我来说,它有点丑陋。我倾向于为例外情况保留例外情况 - 即在正常运作期间不应该发生罕见情况。