【问题标题】:C++ exception handling from constructors without try/catch blocks没有 try/catch 块的构造函数的 C++ 异常处理
【发布时间】:2021-04-08 06:36:32
【问题描述】:

在我目前正在进行的一个学校项目中,我们必须写入和读取二进制文件。 为了使我的代码更优雅,我选择在其中一个类中使用序列化 ctor,这些类接收一个 ifstream 以从文件中读取以获取其适当的数据。

我们的一个要求是我们需要检查文件是否成功读取,但还不允许以 try-catch 块的形式实现异常处理。因此,如果读取不成功,我能想到的唯一选择是返回一个布尔值。问题是我有嵌套类,例如第一个类看起来像:

class A
{
public:
    A(ifstream& input)
    {
      // read data from input //
      // call load method of nested class B  // 
     }

private:
   // some data // 
   B b;
};

并且嵌套类将调用其加载方法来读取适当的数据,而不是具有接收文件的 ctor,因此 B 将如下所示:

class B
{
public:
   void load(ifstream& input);

private:
    // some data // 
};

处理异常的替代方法是什么,或者您建议对我的代码进行哪些更改以使其符合项目要求?

将 B 的加载方法设置为布尔值,并返回 false,例如,如果读数不好会导致我回到 A 的 ctor,我无法从中返回值。

我现在已经用 C++ 编码了几个月,所以如果这里的某些东西看起来像是糟糕的设计或明显错误,我深表歉意 - 也将感谢任何反馈。谢谢。

【问题讨论】:

  • 将所有读取代码从构造函数移到必须显式调用的函数中。
  • Class 应该是classClass B; 不是成员的声明,您缺少;。我修好了那些
  • 读取文件失败应该怎么办?
  • 不确定我的编辑是否正常。你说“嵌套类”,所以我将其设为成员的更改可能会关闭
  • 构造函数报告错误的主要方式是抛出异常。如果你不能从构造函数中抛出异常,最好避免在构造函数中读取文件(本质上容易出错),而是在另一个可以返回值而不是抛出的函数中读取。另一种方法是设置一些对象状态以指示错误条件 - 如果成员函数忘记检查错误条件并且表现得好像没有发生错误,那么问题几乎总是某种形式的错误(例如未定义)行为。

标签: c++ exception constructor try-catch


【解决方案1】:

默认情况下,iostream 没有启用异常。并启用例外 std::istream 对象通常是一个非常糟糕的主意。所以我不会指望它是 活跃。

std::ifstream 更改为std::istream。这将使代码必须更多 灵活的测试,因为它不再需要ifstream,您可以在调用代码时使用std::istringstream 进行测试。

建议将load的API改为

   std::istream& load(std::istream& input);

这与大多数其他 iostream 运算符一致,它只返回 input 变量。它使调用者更容易管道调用。

A::A(std::istream& input) 中的代码可以在调用b.load(input); 之后和/或之前使用goodeoffail 和/或bad 检查结果 在input。这应该足以决定您的bool 应该如何设置 传达适当的失败。

【讨论】:

    【解决方案2】:

    如果你不能从构造函数中抛出,那么你可能需要一个允许返回 "error" 的静态工厂。

    有针对std::expected<T, Error> 的建议,但尚不可用,某些库可能会提出等效建议。

    目前可能的方法包括使用std::optional 或(智能)指针:

    class B
    {
        B() = default;
    public:
        static std::optional<B> load(std::istream& input)
        {
            B b;
            // read input...
            if (errorDetected) { return std::nullopt; }
            return b;
        }
    
    private:
        // some data
    };
    class A
    {
    public:
        static load(std::istream& input)
        {
            A a;
            // read data from input
            if (errorDetected) { return std::nullopt; }
    
            auto b = B::load(input);
            if (!b) return std::nullopt;
            a.b = std::move(*b);
    
            return a;
        }
    
    private:
       // some data
       B b;
    };
    

    如果没有stdoptional 可能会很快实现(有几个注意事项,但它可能已经足够好了),例如:

    template <typename T>
    class my_poor_optional
    {
        T data; // required to be constructed, even when invalid contrary to real optional
        bool is_valid = false;
    public:
        // ...
    };
    

    【讨论】:

      【解决方案3】:

      我会为AB 添加流式运算符(operator&gt;&gt;operator&lt;&lt;),而不是使用转换构造函数和load 成员函数。

      例子:

      class B {
      public:
          friend std::istream& operator>>(std::istream& is, B& b) {
              return is >> b.b_data;
          }
      
          friend std::ostream& operator<<(std::ostream& os, const B& b) {
              return os << b.b_data;
          }
      
      private:
          int b_data;
      };
      
      class A {
      public:
          friend std::istream& operator>>(std::istream& is, A& a) {
              return is >> a.a_data
                        >> a.b;             // uses B's operator>>
          }
      
          friend std::ostream& operator<<(std::ostream& os, const A& a) {
              return os << a.a_data << ' '
                        << a.b;             // uses B's operator<<
          }
      
      private:
          int a_data;
          B b;
      };
      

      然后您可以从文件流式传输并检查流的状态以查看提取是否有效:

          // ...
          A foo;
      
          if(input >> foo) { // input is true in boolean contexts if extraction succeeds
              std::cout << "Successfully read an A: " << foo;
          } else {
              std::cerr << "Failed reading from stream\n";
          }
      

      【讨论】:

        【解决方案4】:

        如果您想确定 IO 操作不应在错误时抛出异常,您可能需要查看 exception masksbasic_ios

        【讨论】:

        • 这并没有解决更严重的问题,即如果发生 I/O 错误(例如,如果对象的某些成员从文件中成功读取,而某些由于 I/O 错误而未初始化)。无论 I/O 操作是否抛出,都可能发生此类问题。
        • 默认情况下,流不会抛出异常。除非您已经弄乱了异常掩码,否则无需弄乱异常掩码。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多