[英]C++11 Return value and error pair
当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; }
};
这种方法具有以下主要缺点:
当您忘记检查结果时,它会使您的代码库有可能出现静默故障。
请考虑以下代码,作为客户端代码示例:
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
类在获取大小之前检查内部状态)。
使用这种方法,每当编写客户端代码时,都必须明确记住要处理错误。 在大多数实际情况下, 您将假定File :: size在调用低级文件size函数之前检查状态 。
不要假设-这会导致错误。
它会严重夸大所有客户端代码,努力完成编译器的工作。 考虑以下替代客户端代码:
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
类没有一个。 如果改用异常,则这不是问题:无论是否对异常进行了测试(捕获块),异常都会传播。
除非有充分的理由避免异常, 否则应将其用于错误处理 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.