GetLastError()是一种设计模式吗?这是好机制吗?

zar*_*zar 19 c++ winapi mfc design-patterns coding-style

Windows API使用GetLastError()机制来检索有关错误或失败的信息.我正在考虑使用相同的机制来处理错误,因为我正在为专有模块编写API.我的问题是API更好地直接返回错误代码?不会GetLastError()有任何特别的优势?考虑下面的简单Win32 API示例:

HANDLE hFile = CreateFile(sFile,
    GENERIC_WRITE, FILE_SHARE_READ,
    NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

if (hFile == INVALID_HANDLE_VALUE)
{
    DWORD lrc = GetLastError();

    if (lrc == ERROR_FILE_EXISTS)
    {
          // msg box and so on
    }
}
Run Code Online (Sandbox Code Playgroud)

当我编写自己的API时,我意识到GetLastError()机制意味着CreateFile()必须在所有出口点设置最后一个错误代码.如果有许多退出点并且其中一个可能错过,则这可能会有一点容易出错.愚蠢的问题,但这是如何完成或有某种设计模式呢?

另一种方法是为函数提供一个额外的参数,它可以直接填写错误代码,因此GetLastError()不需要单独的调用.另一种方法可以如下.我将坚持使用上面的Win32 API,这是分析器的好例子.在这里,我将格式更改为此(假设).

result =  CreateFile(hFile, sFile,
    GENERIC_WRITE, FILE_SHARE_READ,
    NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

if (result == SUCCESS)
{
   // hFile has correct value, process it
}
else if (result == FILE_ALREADY_EXIT )
{
   // display message accordingly
  return;
}
else if ( result == INVALID_PATH )
{
   // display message accordingly.
  return;
}
Run Code Online (Sandbox Code Playgroud)

我的最终问题是从API或甚至只是函数返回错误代码的首选方法是什么,因为它们都是相同的?

Fre*_*abe 20

总的来说,这是一个糟糕的设计.这不是特定于Windows的GetLastError功能,Unix系统与全局errno变量具有相同的概念.这是因为它是隐含的函数的输出.这有一些令人讨厌的后果:

  1. 同时执行的两个函数(在不同的线程中)可能会覆盖全局错误代码.因此,您可能需要具有每线程错误代码.正如对这个答案的各种评论所指出的,这正是做什么GetLastErrorerrno做什么 - 如果你考虑为你的API使用全局错误代码,那么你需要做同样的事情,以防你的API可以从多个线程使用.

  2. 如果外部函数覆盖内部设置的错误代码,则两个嵌套函数调用可能会丢弃错误代码.

  3. 忽略错误代码非常容易.事实上,实际上很难记住它存在,因为并非每个函数都使用它.

  4. 当你自己实现一个功能时,很容易忘记设置它.可能有许多不同的代码路径,如果您不注意,其中一个可能允许控制流转义而无需正确设置全局错误代码.

通常,错误条件是例外.它们并不经常发生,但它们可以.您需要的配置文件可能无法读取 - 但大部分时间都是如此.对于此类异常错误,您应该考虑使用C++异常.任何值得它的C++书籍都会列出为什么任何语言(不仅仅是C++)中的异常都是好的原因,但在激动之前有一个重要的事情需要考虑:

例外展开堆栈.

这意味着当你有一个产生异常的函数时,它会传播给所有调用者(直到它被某人捕获,可能是C运行时系统).这反过来会产生一些后果:

  1. 所有调用者代码都需要知道异常的存在,因此所有获取资源的代码必须能够释放它们,即使面对异常(在C++中,'RAII'技术通常用于解决它们).

  2. 事件循环系统通常不允许异常转义事件处理程序.在这种情况下,处理它们没有好的概念.

  3. 处理回调的程序(例如普通函数指针,甚至是Qt库使用的'signal&slot'系统)通常不希望被调用的函数(一个槽)可以产生异常,因此它们不会打扰想抓住它.

底线是:如果您知道他们在做什么,请使用例外.因为你似乎对这个话题比较新,所以现在坚持返回函数的代码,但请记住,这通常不是一个好的技巧.在任何一种情况下都不要使用全局错误变量/函数.

  • Windows相同 - 'GetLastError'返回每线程值.如果没有,那将是不可用的. (5认同)
  • 你对GetLastError的许多批评都是假的.这是本地的线程.嵌套函数?您必须检查每个API调用的错误,嵌套是无关紧要的.忽略错误代码很容易吗?除了使用错误代码之外,针对没有异常的语言的API无法做很多其他事情. (5认同)
  • @DavidHeffernan:你写'必须在每次API调用时检查错误' - 但不是每个API调用都设置了最后一个错误!考虑Windows套接字API.无论语言是什么,这都是一个糟糕的设计.如果它是返回值的一部分会更好 - 这样,至少每个函数的输入/输出都清晰可见.设计的整个*点*是为了使正确的事情变得简单而且不正确的事情很难做到.全球价值并不是那么好. (3认同)
  • `errno` 被设计成一个宏,它扩展到一些线程本地错误代码。总的来说,你是对的。 (2认同)
  • "通常,错误条件是特殊的".我相信使用异常报告失败的最有用的特性是函数*提供成功作为后置条件*.如果紧接该方法执行后的代码,您知道该方法已完成其工作.无需对空值,超出范围的值,空缓冲区等进行详细而复杂的防御性检查. (2认同)

Sti*_*sis 7

GetLastError到目前为止,该模式最容易出错,而且最不受欢迎.

enum到目前为止,返回状态代码是更好的选择.

你没有提到的另一种选择,但很受欢迎,就是为失败案例抛出异常.如果你想做正确的事情(而不是泄漏资源或让对象处于半设置状态),这需要非常仔细的编码,但是会产生非常优雅的代码,其中所有核心逻辑都在一个地方并且错误处理整齐地分开了.

  • 如果您使用"普通"C++技术(即RAII并仔细阅读标准库),则异常易于使用.我同意这个隐含的事实,即C++的大多数困难都来自于编写异常安全代码的需要. (3认同)