【问题标题】:Error Handling Paradigms: Mixing Exceptions and Error Codes错误处理范例:混合异常和错误代码
【发布时间】:2011-08-08 03:24:27
【问题描述】:

我目前正在使用各种中间件(包括 OGRE(图形)、Bullet(物理)和 OpenAL(声音))在 C++ 中开发游戏引擎,而且我还处于项目初期。正是在这一点上,我正在设置我的错误处理机制。

我计划通过将引擎包含在 DLL 中并导出一个函数来向用户公开该引擎,该函数将返回一个指向主引擎对象的指针。该对象将包含您可以访问引擎的各种组件的方法 - 大多数对象将通过接口访问,因此用户对实际实现是隐藏的。

我倾向于使用错误代码作为我的错误报告机制而不是异常

  1. 通过 DLL 链接传递异常会增加错误报告的复杂性,迫使我导出异常类等,并且
  2. 错误代码通常更有效,因此它们在实时游戏引擎中很常见。

唯一出现的问题是构造函数——它们不能返回错误代码。因此,我计划对所有方法使用错误代码,但在构造函数失败时抛出异常。由于我计划使用工厂方法模式来生成对象并将它们传递给用户代码,因此异常将由引擎在内部处理,并且用户只会在构造失败时得到一个空指针。我意识到您通常不应该混合使用异常和返回码,但替代方案听起来也好不到哪里去:

  • 您无法在构造函数中执行任何操作并使用某种 init() 方法,但 RAII 出现了。
  • 您可以设置一个标志并使用某种 isOk() 或 isInitialized() 方法,但现在您已经介绍了对象存在但未能初始化的某些僵尸状态的可能性,并且您可能忘记检查确保它还活着。

我了解这两种系统的固有成本和收益,并意识到将两者混合通常是个坏主意。但是,既然构造函数不能有某种返回值,那么当构造函数失败并在别处使用错误码时,会不会是不合理的抛出异常呢?谁有更好的建议?

【问题讨论】:

  • 为什么要使用 DLL?为什么不是静态库?
  • @unaperson:灵活性、模块化、“推荐性”,它是一个被多个应用程序使用的库,等等……为什么不使用 DLL?
  • @rubenvb 静态库可以做到这一切。 DLL 是对 20 年前不再存在的问题(磁盘空间有限)的解决方案。
  • 如果您从不将异常传播到内部工厂代码之外,那么问题出在哪里?您不会将异常与错误代码混为一谈,因为没有人会看到您的异常。
  • @unaperson:不,他们不会:你不能用更新版本替换静态库而不替换链接到它的所有应用程序,你不能为一个实例共享相同的内存静态库(链接到它的每个应用程序都有自己的库副本),对于大型项目:您必须完全重新链接所有代码,而不仅仅是重新链接 DLL。当然,我不必链接到这样的东西:en.wikipedia.org/wiki/Dynamic_link_library#Features_of_DLL。我并不是说 DLL 是最好的,我只是说它们仍然可以解决不同的问题。

标签: c++ design-patterns dll exception-handling error-handling


【解决方案1】:

我个人更喜欢例外(并且有很多充分的理由)。但当然,从 DLL 代码向用户代码抛出异常(反之亦然)是个坏主意。因此,在您的情况下,我将在模块(DLL 和可执行文件)中使用异常,并使用基于 error_return 的 API 进行 DLL 导出。当使用 DLL 的基于 error_return 的 API 时,我会为那些 error_return 函数使用基于异常的包装器。

IMO 以任何方式(通过返回值或通过引用或通过每个线程的错误代码)返回错误指示符,用无休止的错误检查分支将代码弄乱了。我的方式(以及我对 C++ 方式的理解)只有在成功的情况下才会返回。

【讨论】:

  • 在 DLL 中处理异常的绝佳建议。但是,您对由错误代码引起的无休止的条件检查提出了如此强烈的观点,我认为我会将引擎设为静态库,以便我可以正确使用异常。感谢您的帮助。
【解决方案2】:

在您的情况下,我会说拧 RAII 并使用 Init 方法。混合使用两种范式不仅会降低一致性,还会迫使您的引擎的消费者在某些人不愿意的情况下实施异常处理机制。

【讨论】:

    【解决方案3】:

    我现在更喜欢只处理异常,但如果您仍然需要或想要在构造函数中使用错误代码,我喜欢的一种方法是强制用户在构造函数中传递参数:

    class Foo {
        public:
            Foo(int &errorCode) {
                //our init code
                rc = SOMETHING_BAD;
            }
    };
    
    //on a method or function:
    int errorCode;
    Foo foo(errorCode);
    if(errorCode != RESULT_OK)
    {
          //handle it
    }
    

    我认为这比使用 IsOk() 方法或单独的 Init 方法要好,因为它至少强制用户将变量作为参数传递并且很难忘记检查错误,缺点是有时它仅仅为此声明变量很无聊。

    我的 2 美分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-22
      • 2017-05-19
      • 2014-04-06
      • 2022-12-15
      • 2012-09-15
      • 2012-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多