需要例外
在上一节处理错误的课程中,我们讨论了使用assert(),cerr()和exit()来处理错误的方法。但是,我们对另一个我们将要讨论的主题进行了遗留:例外情况。
返回代码失败时
在编写可重用代码时,错误处理是必要的。处理潜在错误的最常见方法之一是通过返回码。例如:
int findFirstChar(const char* string, char ch)
{
const std::size_t stringlength{ strlen(string) };
// 逐步执行字符串中的每个字符
for (std::size_t index=0; index < stringlength ; ++index)
// 如果字符与ch匹配,则返回其索引
if (string[index] == ch)
return index;
// I如果未找到匹配项,则返回-1
return -1;
}
此函数返回字符串中匹配ch的第一个字符的索引。如果找不到该字符,则该函数返回-1作为错误指示符。
这种方法的主要优点是它非常简单。但是,使用返回代码有许多缺点,在非常重要的情况下使用时很快就会变得明显:
首先,返回值可能是神秘的 - 如果函数返回-1,它是否尝试指示错误,或者实际上是一个有效的返回值?如果不深入了解函数的内容,通常很难说清楚。
其次,函数只能返回一个值,那么当你需要返回函数结果和错误代码时会发生什么?考虑以下功能:
double divide(int x, int y)
{
return static_cast<double>(x)/y;
}
此函数迫切需要一些错误处理,因为如果用户为参数y传入0,它将崩溃。但是,它还需要返回x / y的结果。它怎么能两个都做?最常见的答案是结果或错误处理必须作为参考参数传回,这使得丑陋的代码不太方便使用。例如:
#include <iostream>
double divide(int x, int y, bool &success)
{
if (y == 0)
{
success = false;
return 0.0;
}
success = true;
return static_cast<double>(x)/y;
}
int main()
{
bool success; // 我们现在必须传入一个bool值来查看调用是否成功
double result = divide(5, 3, success);
if (!success) //并在我们使用结果之前检查它
std::cerr << "An error occurred" << std::endl;
else
cout << "The answer is " << result << '\n';
}
第三,在许多事情可能出错的代码序列中,必须不断检查错误代码。请考虑以下代码片段,其中涉及解析文本文件中应该存在的值:
std::ifstream setupIni("setup.ini"); // 打开setup.ini进行阅读
// 如果无法打开文件(例如因为它丢失了),则返回一些错误枚举
if (!setupIni)
return ERROR_OPENING_FILE;
// 现在从文件中读取一堆值
if (!readIntegerFromFile(setupIni, m_firstParameter)) // 尝试从文件中读取一个整数
return ERROR_READING_VALUE; // Return enum value indicating value couldn't be read
if (!readDoubleFromFile(setupIni, m_secondParameter)) // 尝试从文件中读取一个double
return ERROR_READING_VALUE;
if (!readFloatFromFile(setupIni, m_thirdParameter)) // 尝试从文件中读取一个浮点数
return ERROR_READING_VALUE;
我们尚未涵盖文件访问权限,因此如果您不理解上述工作方式,请不要担心 - 只需注意每个调用都需要进行错误检查并返回给调用者。现在想象一下,如果有20个不同类型的参数 - 你实际上是在检查错误并返回ERROR_READING_VALUE二十次!所有这些错误检查和返回值使得确定哪些函数是试图做更难辨别。
第四,返回代码不能很好地与构造函数混合。如果您正在创建一个对象并且构造函数中的某些内容出现灾难性错误会发生什么?构造函数没有返回类型来传回状态指示器,并且通过引用参数传回一个是混乱的,必须明确检查。此外,即使您这样做,仍然会创建对象,然后必须处理或处理该对象。
最后,当错误代码返回给调用者时,调用者可能并不总是能够处理错误。如果调用者不想处理错误,则必须忽略它(在这种情况下它将永远丢失),或者将错误向上返回到调用它的函数。这可能是混乱的,并导致许多上述相同的问题。
总而言之,返回码的主要问题是错误处理代码最终错综复杂地链接到代码的正常控制流。这反过来又会限制代码的布局方式,以及如何合理地处理错误。
例外
异常处理提供了一种机制,可以将错误或其他异常情况的处理与代码的典型控制流分离。这允许更自由地处理在给定情况下何时以及如何处理最有用的错误,从而减轻返回代码引起的许多(如果不是全部)混乱。
在下一课中,我们将了解异常在C ++中的工作原理。