【问题标题】:C++ constructor exception handling for stack object堆栈对象的 C++ 构造函数异常处理
【发布时间】:2013-11-07 11:19:10
【问题描述】:

假设我想要一个堆栈分配的对象,该对象可能在构造过程中抛出,但又想在调用站点处理异常,我如何使该对象可以从构造它的 try 块外部访问?

例如

class MyThrowingClass {

MyThrowingClass() {throw exception();}

doSomethingImportant() {
//...
}

};

int main() {

//Need to catch the exception:
try {
MyThrowingClass myObj;
} catch() {
//actually handle the error
//...
}

//Also need to use myObj later on
myObj.doSomethingImportant();//but we can't use it here because it was scoped to the try block...
}

如果我将 myObj 封装在 try 中,则 try 范围之外的任何内容都无法看到它,但我不想在其中包含其他所有内容,因为这样代码就变成了 30 级嵌套的 try 块,这就是应该使用 init 函数错误代码的替代方法来删除异常处理。

我无法在构造函数中处理异常,因为对异常的反应取决于使用 MyThrowingClass 的上下文。

显然这个问题可以通过一个

MyThrowingClass* pMyObj;

然后能够包装

pMyObj = new MyThrowingClass();

但这肯定也可以通过堆栈分配的对象来实现吗?

是做类似事情的唯一解决方案

MyThrowingClass myObj;

try {
    myObj.init();
} catch(...) {
//...
}

此时我们基本上回到了与错误代码一样糟糕的状态,并且有一个未初始化或部分初始化的对象。

请注意,这不是一个全局对象,我希望有一些可以在很多地方实例化的东西。

拥有一个包含整个作用域的 try 块(这里是 main 内的所有内容)并在该 try 块的末尾捕获处理每个可能的异常而不是能够处理的捕获块真的是理想的解决方案吗?异常靠近他们的网站?

int main() {
try {

//absoultely everything

}
catch (exceptionTypeA &a) {
//...
}
catch exceptionTypeB &b) {

}

}

【问题讨论】:

  • 理想情况下,您会将所有对myObj 的引用放在 try 块中。否则,您的指针解决方案是唯一的选择。

标签: c++ exception scope stack allocation


【解决方案1】:

如何使对象可以从构造它的 try 块外部访问?

如果构造失败,则对象不存在;所以没有什么可以访问的。

这肯定也可以通过堆栈分配的对象来实现吗?

自动(即堆栈分配)对象仅初始化一次,因此即使您处理了异常,也无法返回并尝试重新初始化它。如果您确实希望能够重试,那么您将不得不使用更复杂的东西,例如您建议的动态分配或两阶段初始化。另一种选择是boost::optional(或者,从明年开始,std::optional),它允许您在一块自动存储中随意创建和销毁对象。

拥有一个包含整个作用域的 try 块真的是理想的解决方案吗?

在典型情况下,异常不在本地处理并且初始化失败表示不可恢复的错误,是的。在您的特殊情况下,您可以在本地处理并恢复,不。

【讨论】:

  • 好的,这很好地说明了如果初始化失败,堆栈分配的对象就不可能再次构造,所以即使有对象失败的信息,你也无法修复它。因此,从对象派生的所有使用都应该在 try 块中,因为这是保证对象在您尝试使用它时存在的唯一方法。初始化指针的方式可以再试一次,这样就可以在try{}的得分之外使用。谢谢。
【解决方案2】:

try 旨在限定可以因某种原因抛出的对象。通过解决它,您正在规避它试图保护您免受伤害的东西(使用定义不明确的对象。)考虑使用函数来生成对象。通过使用noexceptmove构造函数,可以保证移出对象是安全的:

class MyThrowingClass {
  public:
    MyThrowingClass() {
        throw exception();
    }

    // throw() is *okay* if you don't have noexcept
    MyThrowingClass(const MyThrowingClass && other) noexcept { 
    }

};


MyThrowingClass GetObj() {
    try {
        return std::move(MyThrowingClass());
    } catch(...) {
        // return some well defined default or terminate program
    }
}

int main() {
    MyThrowingClass myObj(std::move(GetObj()));
}

【讨论】:

    【解决方案3】:

    假设我想要一个堆栈分配的对象,该对象可能在构造过程中抛出,但又想在调用站点处理异常,我如何使该对象可以从构造它的 try 块外部访问?

    基本上,你不能。至于将所有代码包装在 try 块中是好是坏,这取决于“所有代码”的大小 - 十几行左右没什么大不了的。

    如果初始化程序抛出,你真的想调用MyThrowingClass::doSomethingImportant() 吗?除非您以某种方式保证修复 catch 中损坏的初始化,否则您将在部分初始化的对象上调用方法。

    doSomethingImportant() 的调用包含在与对象的构造相同的try 块中,可以准确地告诉您异常的设计目的是什么:如果出现问题,请跳过以下代码(取决于前面的代码)代码)到错误处理程序。

    【讨论】:

    • 我认为这里的关键是@mike-seymour 确定的:如果构造函数失败,则无法修复对象,因此能够从尝试范围之外访问对象{} 毫无意义/危险。如果您可以修复它,那么这将使它成为一个理想的功能,就像修复对象可行的指针情况一样。
    猜你喜欢
    • 1970-01-01
    • 2016-01-08
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-08
    • 1970-01-01
    相关资源
    最近更新 更多