代码之家  ›  专栏  ›  技术社区  ›  no one special

C++11返回值和错误对

  •  1
  • no one special  · 技术社区  · 8 年前

    由于C++11引入了移动语义,我想知道该函数是否可以同时返回值和操作状态。实际上,实施起来并不困难,但我即将开始一个新的大型项目,不知道我是应该这样做,还是使用一些老式的东西。所以我很好奇你的意见。

    请考虑以下符号:

    class File
    {
        FILE *file = nullptr;
    
        public:
    
            Result<void> open(const char *fileName);
            Result<void> close();
            Result<size_t> size();
            Result<void> seek(size_t newPosition);
            Result<size_t> position();
            Result<char> readCharacter();
    };
    

    现在让我们分析一个使用示例:

    Result<void> processFile(const char *fileName)
    {
        File file;
        auto result = file.open(fileName);
        if (!result.isSuccess())
            return result;
    
        auto fileSize = file.size();
        if (!fileSize.isSuccess())
            return fileSize;
    
        for (size_t i = 0; i < fileSize; i++) {
            auto character = file.readCharacter();
            if (!character.isSuccess())
                return character;
    
            if (character < 'a' || character > 'z')
                return Error::invalidData;
    
            // processfileCharacter(character);
        }
    
        return Error::success;
    }
    

    如您所见,错误管理变得非常简单。此外,当我只编写头代码时,启用优化后,GCC和MSVC都会生成非常优化的代码。我非常喜欢这个符号,没有看到任何严重的缺点。但我很想听听你的意见。

    实施

    如果您想测试它,请欣赏代码:

    enum class Error: int
    {
        success,
        unknown,
        invalidData
        // ...
    };
    

    结果类别:

    template <typename Type = void>
    class Result
    {
        Error error = Error::success;
        Type data;
    
        public:
    
            Result() = default;
    
            Result(Result &&result) = default;
            Result(const Result &result) = default;
            template <typename OtherType> Result(const Result<OtherType> &result) : error(result.error) {}
    
            Result & operator =(Result &&result) = default;
            Result & operator =(const Result &result) = default;
            template <typename OtherType> Result & operator =(const Result<OtherType> &result) { error = result; return *this; }
    
            Result(const Type &data) : data(data) {}
            Result(Type &&data) : data(std::move(data)) {}
            Result(const Error &error) : error(error) {}
            Result(Error &&error) : error(std::move(error)) {}
    
            operator Type& () { return data; }
            operator const Type& () const { return data; }
            operator const Error() const { return error; }
    
            bool isSuccess() const { return error == Error::success; }
    };
    

    void专用化:

    template <>
    class Result<void>
    {
        Error error = Error::success;
    
        public:
    
            Result() = default;
    
            Result(Result &&result) = default;
            Result(const Result &result) = default;
            template <typename OtherType> Result(const Result<OtherType> &result) : error(result.error) {}
    
            Result & operator =(Result &&result) = default;
            Result & operator =(const Result &result) = default;
            template <typename OtherType> Result & operator =(const Result<OtherType> &result) { error = result; return *this; }
    
            Result(const Error &error) : error(error) {}
            Result(Error &&error) : error(std::move(error)) {}
    
            operator const Error() const { return error; }
    
            bool isSuccess() const { return error == Error::success; }
    };
    
    1 回复  |  直到 8 年前
        1
  •  4
  •   utnapistim    8 年前

    这种方法有以下主要缺点:

    • 当您忘记检查结果时,它会使您的代码库有可能出现无声故障。

      请考虑以下代码,作为客户端代码示例:

      Result<void> processFile(const char *fileName)
      {
          File file;
          auto result = file.open(fileName);
          // utnapistim was tired when writing this code and forgot to
          // check the error status in result
          // (this is a bug)
      
          auto fileSize = file.size(); // (1)
          if (!fileSize.isSuccess())
              return fileSize;
      
          // ...
          // rest is the same as your example client code
      
          return Error::success;
      }
      

      缺失错误检查下面的代码以静默方式失败:它将在不应该执行的情况下执行,数据无效。

      在这种特殊情况下,执行的代码(第行 (1) )在 File 班级,还有这个 能够 正常工作(如果 文件 类在获取大小之前检查内部状态)。

      使用这种方法,无论何时编写客户机代码,都必须明确记住处理错误。在大多数实际情况下, 您将假定File::size在调用低级文件大小函数之前检查状态 .

      不要想当然——这会导致bug。

    • 它严重膨胀了所有客户端代码,努力完成编译器的工作。考虑以下备选客户端代码:

      void processFile(const char *fileName)
      {
          auto file = File{fileName}; // throws on failure
          auto fileSize = file.size(); // executed only on success
      
          for (size_t i = 0; i < fileSize; i++) {
              auto character = file.readCharacter(); // throws on failure
      
              if (character < 'a' || character > 'z')
                  throw invalidData{'expected alphanumeric value'};
      
          // processfileCharacter(character);
          }
      
          return Error::success;
      }
      

      您需要维护的客户端代码更少,代码看起来更简单

      您有不变量(当您低于 文件 实例,您 知道 它是有效的,无需添加 if 声明)

    • 这严重限制了面向对象设计的良好原则:

      • 当构造函数无法运行时会发生什么?你的 文件 班上没有。

    如果改用异常,则这不是问题:无论您是否对异常进行了测试(catch块),都会传播异常。

    除非你有充分的理由避免例外, 您应该使用它们来处理错误 .