C 11返回值和错误对

C++11 Return value and error pair

本文关键字:错误 返回值      更新时间:2023-10-16

作为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; }
};

此方法具有以下主要缺点:

  • 当您忘记检查结果时,它将您的代码基础为无声失败的可能性开放。

    而是将此代码视为客户端代码示例:

    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类会在获得大小之前检查内部状态)。

    使用您的方法,每当您编写客户端代码时,都必须明确记住要处理错误。在大多数实际情况下,您将在调用低级文件大小函数之前假设该文件::大小检查状态

    不要假设 - 它导致错误。

  • 它严重夸大了所有客户端代码,努力完成编译器的工作。考虑此替代客户代码:

    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;
    }
    

    您的客户端代码较少,并且该代码看起来更简单

    您有不变性(当您低于File实例的声明时,您知道它是有效的,而无需添加if语句)

  • 它严重限制了OO设计的良好原则:

    • 当您的构造函数无法运行时会发生什么?您的File课程没有。

如果您使用异常,则这是一个非问题:不管您是否对其进行测试(Catch Block)。

除非您有强大的理由避免例外,您应该使用它们进行错误处理