我将用C ++开始这个新项目,并且正在思考一种不痛苦的错误处理方法。现在,我不打算开始抛出和捕获异常,并且很可能永远不会抛出异常,但我在想 - 即使是常规错误处理,为什么要自己滚动/复制粘贴一个类来描述错误/状态,当我可以使用std::exception
及其子类(或者可能是std::optional<std::exception>
)时?
using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);
是否有人/任何项目以这种方式运作?这是一个荒谬的想法还是有点吱吱作响?
答案 0 :(得分:3)
我不认为你应该构建例外情况,除非你真的打算扔它们。我会推荐一个bool或enum返回类型。阅读代码的人的意图会更清晰,而且速度会更快。但是,如果你构造一个异常,那么其他人会认为他们可以抛出异常并导致整个系统崩溃。
C ++异常在资源管理,触发析构函数和所有这些(RAII)中起着重要作用。以任何其他方式使用它们都会损害性能,并且(更重要的是)会让那些试图维护代码的人感到困惑。
但是,您可以使用与std :: exception无关的状态报告类来执行您想要的操作。人们为#34更快做了太多&#34;代码,当他们不需要时。如果状态枚举不够好,并且您需要返回更多信息,那么状态报告类将起作用。如果它使代码更容易阅读,那么去吧。
除非你实际扔掉它,否则不要把它称为例外。
答案 1 :(得分: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
在vanilla设置中,并且:
[amit@amit tmp]$ ./a.out
code elapsed time: 0s
exception elapsed time: 238985s
optional elapsed time: 33595s
exception elapsed time: 24350
at -O2 settings。
P.S。我个人会使用异常/堆栈展开,因为它认为它是C +的基本部分,可能正如@vsoftco所说。
答案 2 :(得分:0)
要回答您的问题,这不是一个荒谬的想法,您实际上可以使用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>
之类的额外工具来简化它。否则,如果你不能使用标准的异常处理,那就去找一个已经证明自己的解决方案,比如经典的错误代码系统。