我可以/应该使用 std::exception's 进行常规错误处理吗?

Can I / Should I use std::exception's for regular error handling?

本文关键字:常规 错误 处理 我可以 exception std      更新时间:2023-10-16

我将在C++开始这个新项目,并且正在考虑一种不痛苦的错误处理方法。现在,我不会开始抛出和捕获异常,并且很可能根本不会抛出异常,但我在想 - 即使是常规的错误处理,为什么要滚动我自己的/复制粘贴一个类来描述错误/状态,当我可以使用std::exception及其子类(或者可能是std::optional<std::exception>)?

using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);

是否有人/任何项目以这种方式工作?这是一个荒谬的想法还是有点吱吱作响?

我认为你不应该构造异常,除非你真的打算抛出它们。我会推荐布尔或枚举返回类型。对于阅读您的代码的人来说,意图会更清晰,而且速度会更快。但是,如果您构造一个异常,其他人会认为他们可以抛出异常并导致整个系统崩溃。

C++异常在资源管理、触发析构函数和所有这些 (RAII) 中起着重要作用。以任何其他方式使用它们都会损害性能,并且(更重要的是)以后会混淆任何试图维护代码的人。

但是,您可以使用与 std::exception 无关的状态报告类执行所需的操作。人们在不需要的时候为"更快"的代码做了太多的事情。如果状态枚举不够好,并且您需要返回更多信息,则状态报告类将起作用。如果它使代码更易于阅读,那就去做吧。

只是不要称它为例外,除非你真的抛出它。

我认为仅性能可能会有问题。请考虑以下代码:

#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() << "sn";
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() << "sn";
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() << "sn";
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() << "sn";
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。

要回答你的问题,这不是一个荒谬的想法,你实际上可以使用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::p romise 相同的方法,即返回一个std::exception_ptr。在那里,您将能够返回任何内容或异常,同时保留实际的错误详细信息。恢复错误的实际类型可能仍然很棘手。

在同一对象中返回错误或结果

最后,另一种选择是利用Expected<T>提案及其实施。在那里,您将能够在单个对象中返回值或错误,并根据需要处理错误(通过测试错误或常规异常处理),对于函数不返回值的情况有一些特殊性(更多关于堆栈溢出或这个博客)。

如何选择

我个人对这个问题的看法是,如果你打算使用异常,那么按照它们的设计方式使用它们,最终使用一些额外的工具,如Expected<T>,使它更容易。否则,如果您无法使用标准的异常处理,那么请选择已经证明自己的解决方案,例如经典的错误代码系统。