我可以/我应该使用 std::exception 进行常规错误处理吗?
Posted
技术标签:
【中文标题】我可以/我应该使用 std::exception 进行常规错误处理吗?【英文标题】:Can I / Should I use std::exception's for regular error handling? 【发布时间】:2015-07-07 22:38:44 【问题描述】:我打算用 C++ 开始这个新项目,并且正在考虑一种不痛苦的方法来进行错误处理。现在,我不打算开始抛出和捕获异常,而且很可能根本不会抛出异常,但我在想——即使是常规的错误处理,为什么要自己滚动/复制粘贴一个类来描述错误/状态,什么时候我可以只使用std::exception
及其子类(或者可能是std::optional<std::exception>
)?
using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);
是否有人/任何项目以这种方式工作?这是一个荒谬的想法还是有点古怪?
【问题讨论】:
@vsoftco:见编辑。我说的是不抛出异常的错误处理。 有趣...我最近听说谷歌似乎在使用这样的系统,不知道是真是假。如果你的函数必须返回一些东西怎么办?你会使用一对/元组吗? @vsoftco:我们不谈细节......问题只是关于报告错误的用途。但是 - 该函数将有一个输入/输出参数(引用或指针),或者它可能会返回一些案例类(我确信 C++ 现在有类似的东西,比 C 联合更好)。 这是一个有趣的问题,我没有看到您提出的例外情况,但我期待看到您问题的答案。但是imo您在最有用的地方没有使用异常:即在RAII中,您确保通过堆栈展开调用适当的析构函数。你提出的方式在某种意义上类似于 C 的做事方式,比如返回错误代码。 您可能想查看Alexandrescu's Expected<T> 【参考方案1】:我认为你不应该构造异常,除非你真的打算抛出它们。我会推荐一个 bool 或 enum 返回类型。阅读您的代码的人的意图会更清楚,他们会更快。但是,如果你构造了一个异常,其他人就会出现并认为他们可以抛出异常并导致整个系统崩溃。
C++ 异常在资源管理、触发析构函数等 (RAII) 中发挥着重要作用。以任何其他方式使用它们都会损害性能,并且(更重要的是)让以后试图维护代码的人感到困惑。
但是,您可以使用与 std::exception 没有任何关系的状态报告类来做您想做的事情。人们在不需要时为“更快”的代码做了太多的事情。如果状态枚举不够好,并且您需要返回更多信息,那么状态报告类将起作用。如果它使代码更容易阅读,那就去吧。
除非你真的抛出它,否则不要称它为异常。
【讨论】:
这个。即使optional<exception>
非常适合用例,在 throw/catch 上下文之外使用它也会令人困惑。应尽可能避免混淆。【参考方案2】:
我认为仅凭性能可能会出现问题。考虑以下代码:
#include <iostream>
#include <chrono>
#include <ctime>
#include <stdexcept>
#include <boost/optional.hpp>
int return_code_foo(int i)
if(i < 10)
return -1;
return 0;
std::logic_error return_exception_foo(int i)
if(i < 10)
return std::logic_error("error");
return std::logic_error("");
boost::optional<std::logic_error> return_optional_foo(int i)
if(i < 10)
return boost::optional<std::logic_error>(std::logic_error("error"));
return boost::optional<std::logic_error>();
void exception_foo(int i)
if(i < 10)
throw std::logic_error("error");
int main()
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
for(size_t i = 11; i < 9999999; ++i)
return_code_foo(i);
end = std::chrono::system_clock::now();
std::cout << "code elapsed time: " << (end - start).count() << "s\n";
start = std::chrono::system_clock::now();
for(size_t i = 11; i < 9999999; ++i)
return_exception_foo(i);
end = std::chrono::system_clock::now();
std::cout << "exception elapsed time: " << (end - start).count() << "s\n";
start = std::chrono::system_clock::now();
for(size_t i = 11; i < 9999999; ++i)
return_optional_foo(i);
end = std::chrono::system_clock::now();
std::cout << "optional elapsed time: " << (end - start).count() << "s\n";
start = std::chrono::system_clock::now();
for(size_t i = 11; i < 9999999; ++i)
exception_foo(i);
end = std::chrono::system_clock::now();
std::cout << "exception elapsed time: " << (end - start).count() << "s\n";
return 0;
在我的 CentOS 上,使用 gcc 4.7,它的时间是:
[amit@amit tmp]$ ./a.out
code elapsed time: 39893s
exception elapsed time: 466762s
optional elapsed time: 215282s
exception elapsed time: 38436s
在原版设置中,并且:
[amit@amit tmp]$ ./a.out
code elapsed time: 0s
exception elapsed time: 238985s
optional elapsed time: 33595s
exception elapsed time: 24350
在 -O2 设置下。
附:我个人会使用异常/堆栈展开,因为我相信它是 C+ 的基本部分,可能正如 @vsoftco 上面所说的那样。
【讨论】:
嗯,是的,重点是,但您测量的是实际发生错误的时间,而不是没有发生错误的时间。此外,如果我的错误处理代码使用数字状态代码将字符串传递到某处,或者将其写入流,那将等同于我认为的运行时间。 @einpoklum 实际上,我不同意-如果您检查代码,则此测试中从未发生过“异常”。这正是这种方法的问题——对于正常情况来说,它似乎非常昂贵。这也适用于您关于实际处理错误的观点 - 它往往不那么频繁。 (如果你考虑一下,yeshpomashehu :-))【参考方案3】:回答您的问题,这不是一个荒谬的想法,您实际上可以使用std::exception
进行常规错误处理;有一些注意事项。
使用std::exception
作为函数的结果
假设一个函数可以在几种错误状态下退出:
std::exception f( int i )
if (i > 10)
return std::out_of_range( "Index is out of range" );
if ( can_do_f() )
return unexpected_operation( "Can't do f in the current state" );
return do_the_job();
您如何使用std::exception
或可选的来处理这个问题?当函数返回时,将创建异常的副本,只保留std::exception
部分并忽略实际错误的细节;给你留下的唯一信息是“是的,出了点问题……”。该行为与返回预期结果类型的布尔值或可选值(如果有)相同。
使用std::exception_ptr
保存细节
另一种解决方案是应用与std::promise 相同的方法,即返回std::exception_ptr
。在那里,您将能够不返回任何内容或返回异常,同时保留实际的错误详细信息。恢复错误的实际类型可能仍然很棘手。
在同一对象中返回错误或结果
最后另一种选择是使用Expected<T>
proposal 和its implementation。在那里,您将能够在单个对象中返回值或错误,并根据需要处理错误(通过测试错误或使用常规异常处理),对于函数不返回值的情况有一些特殊性(更多Stack Overflow 或 this blog)。
如何选择
我个人对此事的看法是,如果您要使用异常,请按照设计的方式使用它们,最终使用一些额外的工具,如 Expected<T>
以使其更容易。否则,如果你不能使用标准的异常处理,那就去寻找一个已经证明自己的解决方案,比如经典的错误代码系统。
【讨论】:
以上是关于我可以/我应该使用 std::exception 进行常规错误处理吗?的主要内容,如果未能解决你的问题,请参考以下文章
std :: current_exception应该从类的析构函数中的catch块返回非null值
std::exception::_Raise 和 std::exception::exception 上的 VC++ 链接器错误