C ++ 11返回值和错误对

时间:2017-12-20 09:53:37

标签: c++ c++11 error-handling runtime-error

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

虚空专业化:

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 个答案:

答案 0 :(得分:4)

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

  • 当您忘记检查结果时,它会打开代码库以防止出现静音失败。

    请考虑使用此代码,作为客户端代码示例:

    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类上,此可以正常工作(如果{{1}在获取大小之前,class检查内部状态。)

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

    不要假设 - 它会导致错误。

  • 它严重膨胀所有客户端代码,努力完成编译器的工作。考虑这个替代客户端代码:

    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语句)

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

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

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

除非您有充分的理由避免异常,否则应使用它们进行错误处理