【问题标题】:Getting rid of an ugly C construct摆脱丑陋的 C 结构
【发布时间】:2014-06-21 16:17:42
【问题描述】:

我继承了一段(大)代码,该代码具有错误跟踪机制,其中它们将布尔变量传递给它们调用的所有方法,并且在执行的各个阶段出现错误时,该方法将停止并返回,有时是默认值价值。

类似(之前):

#include <iostream.h>
int fun1(int par1, bool& psuccess)
{
    if(par1 == 42) return 43;
    psuccess = false;
    return -1;
}
int funtoo(int a, bool& psuccess)
{
    int t = fun1(a, psuccess);
    if(!psuccess)
    {
        return -1;
    }
    return 42;
}
void funthree(int b, bool& psuccess)
{
    int h = funtoo(b, psuccess);
    if(!psuccess)
    {
         return;
    }
    cout << "Yuppi" << b;
}
int main()
{
    bool success = true;
    funthree(43, success);
    if(!success)
    {
        cout<< "Life, universe and everything have no meaning";
    }
}

请注意,这是 C 和 C++ 代码的混合,正是项目的方式。

现在,C 魔术出现了:“某人”在某处定义了一个宏:

#define SUCCES_OR_RETURN  if(!psuccess) return

上面的程序变成了(AFTER):

#include<iostream.h>
int fun1(int par1, bool& psuccess)
{
    if(par1 == 42) return 43;
    psuccess = false;
    return -1;
}
int funtoo(int a, bool& psuccess)
{
    int t = fun1(a, psuccess);
    SUCCES_OR_RETURN -1;
    return 42;
}
void funthree(int b, bool& psuccess)
{
    int h = funtoo(b, psuccess);
    SUCCES_OR_RETURN ;
    std::cout << "Yuppi" << b;
}
int main()
{
    bool success = true;
    funthree(43, success);
    if(!success)
    {
        cout<< "Life, universe and everything have no meaning";
    }
}

问题:我想知道是否有更好的方法来处理这种错误跟踪,或者我必须忍受这种情况。我个人不喜欢滥用CSUCCES_OR_RETURN 即。一旦用参数调用它,而在其他情况下不带参数调用它,感觉就像一个真正的return 声明,但我没有找到任何更好的解决这个古老设计的方法。

请注意,由于平台限制,我们有几个限制,但无论如何我都愿意听取关于这两个的意见:

  • 抛出异常。该代码是相互调用的 C 和 C++ 函数的混合体,编译器不支持throw(接受语法但不做任何事情,只是一个警告)。此解决方案是在 C++ 环境中解决此问题的标准方法。
  • C++11 特性,这是一个微型嵌入式平台,它带有一个晦涩而古老的“几乎”C++ 编译器,它不是为支持最新的 C++ 特性而设计的。但是,如果有 C++11 提供的任何东西,我会很好奇以供将来参考。
  • 模板魔法。编译器在理解复杂的模板问题时遇到了问题,但我还是愿意看到您能提出的任何解决方案。

编辑

另外,正如@BlueMoon 在推荐中所建议的那样,创建一个全局变量是行不通的,因为在函数链的最开始调用success 变量是一个类的成员变量,并且有几个对象类创建后,他们每个人都需要报告其成功状态:)

【问题讨论】:

  • “我没有找到任何更好的解决方案来解决这个古老的设计。” 也许我遗漏了什么,但为什么不直接使用编辑器的查找/替换功能进行与宏相同的替换?宏在这里的作用是什么?提高可读性?呸!
  • 实际作用是降低可读性...
  • 您已经通过说您的编译器不支持这些选项排除了您想听到的选项?此外,我不明白为什么有人想传递一个变量给每个函数来捕获成功。这不是我们有返回码的原因吗?如果必须这样做,则使用全局变量。如果该变量在所有地方传递,我不明白为什么全局不适合。那个宏是没有意义的。摆脱它!
  • 代码更改越多,您就越成为它的所有者。扩展宏(@Cody Gray)看起来是一个很好的步骤。除此之外,将其全部重新制造或不理会。​​span>

标签: c++ c c++11 macros


【解决方案1】:

这里对混合 C 和 C++ 错误处理策略进行了很好的细分:

引用链接的文章,您的选择主要归结为:

  • 从可能失败的函数中返回错误代码。
  • 提供类似于 Windows 的 GetLastError() 或 OpenGL 的 glGetError() 的函数来检索最近出现的错误代码。
  • 提供一个包含最新错误的全局(希望是线程局部的)变量,例如 POSIX 的 errno。
  • 提供一个函数以返回有关错误的更多信息,可能与上述方法之一结合使用,例如 POSIX 的 strerror 函数。
  • 允许客户端在发生错误时注册回调,例如 GLFW 的glfwSetErrorCallback
  • 使用特定于操作系统的机制,例如结构化异常处理。
  • 将错误写入日志文件、stderr 或其他位置。
  • 发生错误时只需 assert() 或以其他方式终止程序。

您所继承的代码的作者似乎选择了一种相当奇怪的方式,将一个指向布尔值 [原文如此] 的指针传递给函数使用似乎相当不寻常。

文章有一些很好的例子,我个人喜欢这种风格:

libfoo_widget_container_t container = NULL;
libfoo_error_details_t error = NULL;
if (libfoo_create_widgets(12, &container, &error) != libfoo_success) {
    printf("Error creating widgets: %s\n", libfoo_error_details_c_str(error));
    libfoo_error_details_free(error);
    abort(); // goodbye, cruel world!
}

在这里你得到了一点点,传入指向错误类型的指针,与成功常量的比较(而不是0|1,C 和世界其他地方之间痛苦的二分法!)。

我认为用goto 更好地实现您的宏并不过分,无论如何,如果一个函数不止一次调用SUCCES_OR_RETURN,它可能提示该功能做得太多。复杂的清理或返回可能是代码异味,您可以在此处阅读更多内容http://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c/

【讨论】:

  • 是的,确实这段代码从一英里开始就很臭......感谢您的链接!
【解决方案2】:

如果您想使用完整的 C++,答案将是更改异常的“无效返回值”...

#include <iostream>
#include <exception>

using std::exception;

struct error : exception { const char* what() const throw() override { return "unsuccessful"; } };

int fun1(int par1) {
  if( par1 == 42 ) return 43;
  throw error();
}

int funtoo(int a) {
  fun1(a);
  return 42;
}

void funthree(int b) {
  funtoo(b);
  std::cout << "Yuppi " << b << "\n";
}

int main() {
  try {
    funthree(42);
  } catch(exception& e) {
    std::cout << "Life has no meaning, because " << e.what() << "\n";
  }
}

这将打印Yuppi 42(如果您将调用funthree(42) 更改为funthree(43),它将打印Life has no meaning, because unsuccessful...)

(live at coliru)

【讨论】:

    【解决方案3】:

    我以前见过这种错误处理方式。我将其称为错误遗忘手动伪异常。

    代码流程大多是忽略错误的:您可以连续调用 3 个具有相同错误标志的函数,然后查看错误标志以查看是否发生任何错误。

    错误标志充当伪异常,一旦设置,我们就开始“跳过”正常的代码流,但这是手动完成的,而不是自动完成的。

    如果你做某事而不关心是否发生错误,你可以放弃产生的错误并继续。

    The ICU library 以类似的方式处理错误。

    在最大限度地减少结构差异的同时执行此操作的更 C++1y 方法是修改代码以返回 expected 对象。

    expected&lt;T, Err&gt; 应为T,如果出现问题,则改为Err 类型。这可以实现为boost::variant 和C++1y 的std::optional 的混合体。如果您在expected&lt; T, Err &gt; + U 上重载大多数算术运算以返回expected&lt; decltype( std::declval&lt;T&amp;&gt;() + std::declval&lt;U&gt;(), Err &gt; 并小心处理auto,则至少可以允许算术表达式保持其结构。然后,您将在事后检查错误。

    另一方面,如果错误返回值可以根据其类型进行预测,则您可以创建一个类型,该类型在转换为给定类型时会产生错误值。修改返回void 的函数,以便在您使用它时返回某种错误对象。现在每个函数都可以

    if (berror) return error_flag_value{};
    

    这至少摆脱了那个奇怪的;-1; 问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-15
      • 1970-01-01
      • 1970-01-01
      • 2012-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多