c++ - C++ 构造函数中的异常消除

标签 c++ exception

我们最近遇到了将 C++ 框架移植到运行 uClinux 的 ARM 平台的问题,该平台唯一支持供应商的编译器是 GCC 2.95.3。我们遇到的问题是异常非常不可靠,导致从根本没有被捕获到被不相关的线程(!)捕获。这似乎是一个记录在案的错误,即 herehere .

经过深思熟虑,我们决定完全消除异常,因为我们已经达到了异常对正在运行的应用程序造成很大损害的地步。现在主要关心的是如何管理构造函数失败的情况。

我们已经尝试过lazy evaluation ,其中每个方法都能够实例化动态资源并返回一个状态值,但这意味着每个类方法都必须返回一个返回值,这会在代码中产生 lot 的 ifs 并且非常烦人在通常不会导致错误的方法中。

我们考虑添加一个静态 create 方法,该方法返回一个指向已创建对象的指针,如果创建失败则返回 NULL,但这意味着我们不能再将对象存储在堆栈上,仍然需要通过如果您想对实际错误采取行动,请在对状态值的引用中。

根据 Google 的 C++ 风格指南,他们 do not use exceptions并且只在它们的构造函数中做琐碎的工作,使用 init 方法进行非平凡的工作( Doing Work in Constructors )。但是,我找不到任何关于他们在使用这种方法时如何处理构造错误的信息。

这里有没有人尝试过消除异常并想出一个很好的解决方案来处理构造失败?

最佳答案

对于堆栈上的对象,通常你会得到这样的代码:

MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
    // error-handling code
} else {
    // phew, we got away with it. Now for the next object...
}

这适用于堆上的对象。我假设您使用返回 NULL 而不是抛出的东西覆盖全局运算符 new,以节省您记住在任何地方都使用 nothrow new:

MyClassWithNoThrowConstructor *foo = new MyClassWithNoThrowConstructor();
if (foo == NULL) {
    // out of memory handling code
} else if (foo->init(bar, baz, etc) != 0) {
    delete foo;
    // error-handling code
} else {
    // success, we can use foo
}

显然,如果可能的话,使用智能指针来省去记住删除的麻烦,但是如果你的编译器不能正确地支持异常,那么你在获取 Boost 或 TR1 时可能会遇到麻烦。我不知道。

您还可能希望以不同的方式构造逻辑,或抽象组合的 new 和 init,以避免在处理多个对象时出现深度嵌套的“箭头代码”,并在两者之间共享错误处理失败案例。以上只是最苦心形式的基本逻辑。

在这两种情况下,构造函数都将所有内容设置为默认值(它可以接受一些参数,前提是它对这些参数的处理不可能失败,例如,如果它只是存储它们)。然后 init 方法可以完成真正的工作,这可能会失败,在这种情况下返回 0 成功或任何其他失败值。

您可能需要强制整个代码库中的每个 init 方法都以相同的方式报告错误:您确实想要一些返回 0 成功或负错误代码,一些返回 0 成功或正确的错误代码,一些返回 bool,一些返回具有解释错误字段的值的对象,一些设置全局 errno 等。

您或许可以快速查看一些在线 Symbian 类 API 文档。 Symbian 无一异常(exception)地使用 C++:它确实有一种称为“离开”的机制,可以部分弥补这一点,但从构造函数中离开是无效的,因此在设计非失败构造函数和推迟失败方面存在相同的基本问题初始化例程的操作。当然,对于 Symbian,init 例程是允许离开的,因此调用者不需要我上面指出的错误处理代码,但在 C++ 构造函数和额外的 init 调用之间的拆分工作方面,它是相同的。

一般原则包括:

  • 如果您的构造函数想要以可能失败的方式从某处获取值,请将其推迟到 init 并在 ctor 中保留默认初始化的值。
  • 如果您的对象包含一个指针,请在 ctor 中将其设置为 null 并在 init 中“正确”设置。
  • 如果你的对象持有一个引用,要么将其更改为(智能)指针,以便它可以以 null 开头,要么让调用者将值作为参数传递给构造函数,而不是在 ctor 中生成它。
  • 如果你的构造函数有对象类型的成员,那么你很好。他们的 ctor 也不会抛出任何异常,因此以通常的方式在初始化列表中构造您的成员(和基类)是完全可以的。
  • 确保跟踪设置的内容和未设置的内容,以便在初始化失败时析构函数起作用。
  • 除了构造函数、析构函数和 init 之外的所有函数都可以假定 init 已成功,前提是您为您的类提供文件证明在 init 被调用并成功之前调用除 init 之外的任何方法都是无效的。<
  • 您可以提供多个 init 函数,与构造函数不同,它们可以相互调用,就像为某些类提供多个构造函数一样。
  • 您不能提供可能失败的隐式转换,因此如果您的代码当前依赖于引发异常的隐式转换,那么您必须重新设计。大多数运算符重载也是如此,因为它们的返回类型受到限制。

关于c++ - C++ 构造函数中的异常消除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/334439/

相关文章:

c++ - 重载 operator= 不起作用

c++ - 如何调用模板类的析构函数?

C++异常安全

c++ - 函数作用域静态对象的构造函数抛出异常

c# - UnobservedTaskException - 任务从哪里来

C# DateTime ParseExact 异常

c++ - 段错误 GetConsoleScreenBufferInfo (WinApi) C++

C++ 复制一个 vector 结构,但对象不同

python - 如何使用 PyKCS11 库验证签名数据

C# 在调用堆栈中捕获未处理的异常