C++中返回错误代码的正确方法是什么?

31

我在我的C++项目中使用错误代码来处理错误。问题是如何从一个本应返回某些变量/对象的函数中返回错误代码。

考虑以下情况:

long val = myobject.doSomething();

在这里,myobject 是某个类的对象。如果 doSomething 函数遇到一些错误条件,那么应该如何通知调用者 (不使用异常)。

可能的解决方案:

  1. 在类中设置一个数据成员 (比如 err_),可以由调用者检查。但是在共享同一对象并调用相同函数的多线程应用程序中,它是不安全的。
  2. 使用一些全局的错误变量,同样存在多线程环境下的问题。

现在,我该如何通知调用者发生了一些错误条件?


12
顺便说一下,你刚刚发现了异常的最主要原因。但是为什么你还要拒绝报告错误的另一个常见解决方案,即返回错误代码并使用输出参数来表示函数结果? - MSalters
@MSalters 我没有使用您提到的另一种方法,因为一些代码已经完成,这将增加风格上的不一致性。 - Jeet
1
如果您需要返回一个对象,并且不想通过非 const 引用传递该对象,而且没有明显的错误值或者您需要指示发生了什么具体的错误,那么您就需要使用异常,否则事情将变得笨拙。这就是事实。我同意 MSalters 的评论。 - sbi
13个回答

33

创建一个名为Maybe的模板,该模板以返回值类型为参数。每当您返回一个值时,请像这样将其包装在此模板中:

Maybe<long> result = object.somemethod();

Maybe 模板可能有一种通过错误代码进行实例化的方式(可能是一个静态方法):

return Maybe<long>::error(code);

但通常只会返回值:

Maybe<long> retval;
retval = 15;
return retval;

(当然,您需要覆盖适当的构造函数、赋值运算符等。)

在客户端调用一个方法来检查错误。

Maybe<long> result = object.somemethod();
if (result.is_error) 
{ 
    ... handle the error ...
}
else
{
    ... use the result ...
}

需要使用适当的运算符来定义Maybe<long>,以便在需要long的任何地方使用它。

听起来工作量很大,但实际上只需在制作良好、牢固的Maybe模板时完成这项工作。您还需要对其进行一些性能调优以避免不必要的开销。如果想要使其更加灵活,可以将返回值类型和错误类型都参数化。(这只会增加一点复杂度。)


7
这很优雅。您可以通过添加一个到bool的强制转换运算符来扩展它,这样您就可以写成if (result)而不是if (!result.is_error),并在访问结果值时添加一个异常处理,如果结果是一个错误。我知道抛出异常是我们要避免的,但这确实是一个很好的例子。 - D.Shawley
2
我认为当值处于错误状态时访问它是抛出异常的理想情况,因为它显示了明显的程序员错误:没有检查错误返回。 - JUST MY correct OPINION
6
正如我在这个页面的其他地方所说,异常是为了特殊情况(即:意料之外的情况)。返回码用于当你预期错误是程序正常执行的一部分时。你不应该将异常用作通用信号机制。请参考这个列表中的第一项以获取一些指导。异常不是返回码,永远不应该被用作一般性的返回码机制。将它们用作这样的机制会以最糟糕的"COME FROM"方式混淆代码,而且代价很大。 - JUST MY correct OPINION
1
实际上,@Nemanja Trifunovic,如果我要在代码中认真地做这件事,我会使用Either<value,error>模板(我在那里的答案末尾提到了这一点),但是对于普通的Haskell用户来说,“Maybe”单子更容易被识别。 - JUST MY correct OPINION
3
这句话的意思是: boost::optionalboost::variant 是 C++ 中的两个库,它们的运行时成本几乎可以忽略不计,因为你放在那里的值是栈分配的:这些模板包装了一个指示当前存储的值的标志 + 类似于 union 的结构(可能是原始内存,取决于当前对象是否正确对齐)。 - Matthieu M.
显示剩余5条评论

9

6
可以简要概括一下这个1.5小时的演讲吗?Expected<T>基本上是一个带有Tstd::exception_ptrVariant吗? - mic
1
用最简单的例子写一个总结更有帮助,无论如何感谢分享 :) - S.M.Mousavi

8

您可以通过引用传递变量,并在其中返回错误代码。


4
更常见的做法是传递一个变量作为引用,并通过返回错误代码来获取结果。 - adf88
5
这是一种普遍但丑陋的做法。Expected<T> 惯用语是更好的选择。 - Christopher Smith

7
你可以返回一个 std::pair,其中包含错误代码(或错误对象)和所需的返回对象。感兴趣的对象需要有默认构造函数或值,以便在遇到错误时仍然可以返回某些内容。

5

我看到有很多好的解决方案,但如果我遇到这种情况,我会用另一种方法来处理。

auto doSomething()
{        
    // calculations
    return std::make_pair(error_code, value)
}

int main()
{
    auto result = doSomething();
    if (!result.first)
    {
        std::cout << result.second;
    }
    else
    {
        std::cout << "Something went wrong: " << result.second;
    }
}

对我来说,将bool作为引用传递的解决方案比较麻烦。从C++14开始支持auto返回值类型推导。


4

通常会返回一个返回/错误代码,并提供一个包含结果的属性或成员。

int retCode = myobject.doSomething();
if (retCode < 0){ //Or whatever you error convention is
   //Do error handling
}else{
   long val = myobject.result;
}

通常情况下会传递一个指针作为返回值,并返回返回值/错误代码。详情请参见HrQueryAllRows

long val = INIT_VAL;
int retCode = myObject.doSomething(&val);

if (retCode < 0){
    //Do error handling
}else{
    //Do something with val...
}

4

你有三个选项:

  • 创建一个包含返回值和可能的错误代码的类。

  • 使用类似于boost::optional的东西作为返回值,允许出现无效响应。

  • 传递一个变量的引用并在其中返回任何可能的错误代码。


2
最常见的做法是返回错误代码。
long result;
int error = some_obj.SomeMethod(&result);

或者返回一个表示出现错误的值:
long result = some_obj.SomeMethod();
if (result < 0) error = some_obj.GetError();

1
GetError() 方法实际上限制了你编写高质量并发代码的能力(因为你需要在对象中保存方法调用的状态)。 - weberc2

2
我建议采取以下措施:
class foo {
public:
    long doSomething();
    long doSomething(error_code &e);
};

其中, error_code 是一个保存错误的类型。它可以是整数或者基于 boost::system::error_code 的某些东西。

您需要提供以下两个函数:

  1. 第一版本会抛出错误,例如 throw boost::system::system_error ,该错误是从 boost::system::error_code 创建的。
  2. 第二个将错误代码返回到 e 中。

2

返回一个错误句柄。使用错误管理器来保存错误代码和其他信息(例如ERROR_INVALID_PARAMETER和类似ParameterName="pszFileName"的名称-值对)。可以使用该句柄访问这些信息。调用者可以将错误句柄与NO_ERROR_HANDLE进行比较。如果为真,则没有发生错误。调用者可以增加错误信息并将句柄传递到堆栈上层。 错误管理器可以是进程的管理器或每个线程的管理器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接