【问题标题】:Best way to deal "locally" with exceptions thrown in object constructor“本地”处理对象构造函数中抛出的异常的最佳方法
【发布时间】:2020-07-02 22:31:58
【问题描述】:

这个问题我想了好久,但在网上还没有找到足够满意的答案。所以我来了。

假设

为了清楚起见,让我们将自己限制为 C++11 和自定义对象。通过自定义对象,我的意思是开发人员可以完全控制的用户定义的类。下面的所有代码 sn-ps 并不意味着可编译,甚至在语法上也不正确。它们只是说明了一个概念。

边界条件

我们的对象有一个重要的构造函数,如果遇到错误,它可以抛出异常。在构造这个对象时我想在尽可能接近对象创建点的地方捕获和处理异常,以使代码更具可读性并且只捕获构造函数异常,而不捕获其他任何东西。

示例 1

这个例子并不理想,因为这正是我想要避免的:处理远离构造函数的构造函数异常。

class MyClass {
public:
  MyClass() {
    throw 1;
  }
}

int main() {

  try {

    MyClass my_obj;

    try {
      // Do something with my_obj that may throw
    } catch (...) {
      // deal with exceptions
    }

  } catch(...) {
    // deal with constructor exceptions
  }
}

示例 2

这里我使用 std::unique_ptr 来分隔对象声明和初始化。缺点是我现在在堆而不是堆栈上创建对象,即使我没有充分的理由这样做。

class MyClass {
public:
  MyClass() {
    throw 1;
  }
}

int main() {

  std::unique_ptr<MyClass> my_obj_ptr;

  try {
    my_obj_ptr = boost::make_unique<MyClass>();  
  } catch (...) {
    // deal with constructor exceptions
  }

  // continue to use my_obj
}

示例 3

修改对象内部状态并检查。

class MyClass {
private:
  good_internal_state;

public:
  MyClass() : good_internal_state(true) {
    try {
      throw 1;
    } catch(...) {
       good_internal_state = false;
    }
  }

  bool IsInternalStateGood() {
    return good_internal_state;
  }
}

int main() {

  MyClass my_obj;
  if (!my_obj.IsInternalStateGood()) {
    // Do something
  }

  // continue to use my_obj
}

现在我倾向于示例 2 的案例,但我想知道实现我想要的最语法正确的方法是什么。

【问题讨论】:

  • 这个类可以/是否有一个便宜的移动构造函数?
  • 让我们假设,如果该类正在管理一个资源(堆内存、文件、套接字等),它有一个使用copy-and-swap idiom 实现的移动构造函数。否则,它具有默认的移动构造函数。
  • 假设MyClass 的构造失败。在所有情况下,当名为my_obj 的对象可见,但尚未正确构造时,您希望标记为// continue to use my_obj(或等效代码)的代码做什么?如果构造函数抛出异常,则有效构造的对象从未存在过。在构造函数中抛出异常的目的是表明无法构造对象。
  • @Peter 正如你所说,我不能使用尚未创建的对象。我可能会报告错误并返回或类似的东西。请不要过度考虑我写的例子。它们并不意味着在逻辑上是正确的。它们只是展示了捕获构造函数异常的不同方式。
  • 我的观点是,“尽可能接近对象构造点”捕获异常的目标是基于错误的前提。异常的目的是在检测到错误的代码无法纠正原因时发出异常错误情况的信号。如果异常处理程序可以“接近”抛出点,则根本没有必要捕获异常,因为这意味着可以在该点纠正原因,并且从逻辑上讲,意味着不应在全部,更不用说抓到了。

标签: c++ exception constructor


【解决方案1】:

我不会说这些版本中的任何一个或多或少是正确的(除了一些拼写错误)。示例 2 看起来像我会使用的那个。这里范围界定似乎是最大的问题。该变量需要在try 之外声明,但必须在内部进行初始化。如果您不喜欢使用堆 std::optional 可能会有用:

int main() {

  std::optional<MyClass> maybeMyClass;

  try {
    maybeMyClass.emplace(/*constructor parameters*/);
  } catch (...) {
    // deal with constructor exceptions
  }

  // continue to use my_obj
  maybeMyClass->foo();
}

尽管语法可能暗示其他情况,但由std::optional 管理的值被分配为此std::optional 的足迹(在本例中位于堆栈上)。


您也可以使用工厂函数createMyClass,它会返回一个std::optional,这需要MyClass 有一个移动构造函数,这不应该太昂贵。

【讨论】:

    猜你喜欢
    • 2021-10-31
    • 2011-06-25
    • 1970-01-01
    • 2011-10-10
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 2014-05-23
    • 1970-01-01
    相关资源
    最近更新 更多