C++:运算符重载和错误处理

5
我目前正在研究C++中的运算符重载,为一个简单的2D顶点类实现[]运算符以获取位置信息。这通常是有效的,但我不知道如何处理错误,例如如果操作符越界(在仅具有x和y值的2D顶点类的情况下,如果它大于1,则越界)。
在这种情况下,处理错误的常见方法是什么?
谢谢。

这是我现在所做的内容:class ArrayOutOfBounds: public exception{ virtual const char* what() const throw() { return "您要访问的数组越界了!"; } }; T& operator[](uint32 _i){ try{ if(_i>1){ throw exOutOfBounds; } else { return pos[_i]; } } catch (exception& e){ cout << e.what() << endl; exit(0); } } - moka
6个回答

4

当你必须抛出异常时,就必须抛出。除非你能返回某种魔法爆炸结果值,否则无法诊断重载运算符中的问题。定义一个异常类型,在出现错误时抛出它,并对其进行文档说明。


3
错误处理在任何时候都是棘手的问题。它基本上取决于错误的严重程度以及它发生时是否有任何预期行为。
有四种基本的处理方法:
1. 抛出异常 - 错误处理的大杀器。如果需要,一定要使用它,但是如果不小心使用,可能会自食其果。 - 抛出异常后,跳过抛出点和catch之间的所有内容,只留下死亡和破坏。 - 如果没有被捕获,它将中止您的程序。
2. 返回表示失败的值 - 让程序员检查成功与否,并根据情况做出反应。 - 失败的值取决于类型。指针可以返回NULL或0,STL容器可以返回object.end(),否则未使用的值可以使用(例如-1或“”)。
3. 优雅地处理条件 - 有时,一个错误并不是真正的错误,只是一个麻烦。 - 如果仍然可以提供有用的结果,则一个错误可以轻松地被隐藏起来,而不会伤害任何人。 - 例如,一个超出范围的错误可以只返回数组中的最后一个变量,而无需采用任何混乱的异常处理方法。 - 只要它是可预测的和定义的,程序员就可以按照自己的意愿处理它。
4. 未定义的行为 - 嘿,程序员首先不应该给你提供错误的输入。让他们自己承受痛苦吧。
总的来说,我只会在出现能够破坏程序的问题时使用第一种方法,对于那些没有真正预期从中恢复的问题。否则,将异常用作流程控制的形式与回到goto的日子几乎没有什么区别。
第二种方法可能是非程序破坏性错误中最常见的处理方式,但它的有效性实际上取决于您正在处理的返回类型。它很有优势,因为它允许程序员通过检测故障并自行恢复来在本地控制流程。在处理重载运算符时,它的用途有限,但为了完整起见,我认为我应该加入它。
第三种方法非常具体化。许多错误无法以这种方式处理,即使可以,也可能导致不直观的结果。请谨慎使用,并确保进行彻底记录。或者,不要记录它,假装它是第四种选项。
现在,针对提供的具体示例,即重载operator[]时出现超出范围错误,我个人会选择第四种选项。并不是因为我特别喜欢看其他程序员在处理我的代码时遇到困难(顺带一提,我确实喜欢,但这与讨论无关),而是因为这是预期的行为。
大多数情况下,程序员使用operator[]时,他们期望自己处理边界检查,并不依赖于类型或类来为他们做任何事情。即使在STL容器中,你也可以看到operator[](没有范围检查)与否则多余的object.at()进行范围检查)并存。反映出你自己重载运算符的预期行为通常会使代码更加直观。

好的,谢谢,我可能会选择第四个选项!无论如何,你的帖子真的很有帮助! - moka
否则,将异常用作流程控制形式与回到goto时代几乎没有什么区别。哎呀,异常旨在首先解决错误检查问题。它们确保您的代码正确处理错误。未捕获异常将终止程序,这是正确的做法,当您的程序忽略处理错误时(即使只是忽略错误,语言也无法知道它是否严重)。您首选的解决方案2的缺点是很容易忘记或忽略错误检查,从而导致逻辑错误。 - Emily L.

2
根据C++语言FAQ,不应该将operator[]用于矩阵或二维数组的实现;而应该使用operator()点击这里查看FAQ#13.10 问题在于为多个维度实现[]。
至于错误,如果您不想为重载的运算符提供任何额外参数,那么您将不得不走异常路线(使用exception)(使用operator()的另一个原因)。

2
我认为他的意思是一个有两个元素的向量(即二维空间中的一个点),而不是一个二维矩阵。 - Adam Rosenfield

1

我认为加入一个断言可能也是必要的。你是否预见到在二维向量中越界除了(简单?)程序员错误之外还会有其他情况吗?

T& operator[](size_t index)
{
    assert(index < 2 && "Vector index out of bounds");
    return pos[index];
}

如果你要抛出异常,我想你也可以使用out_of_range或者它的派生类型。


1

正如其他人所指出的那样,异常是解决问题的方法。

但是,这似乎是访问点类的一种非常不寻常的习惯用语。我认为更直接的方法是让顶点类具有单独的成员:

class Vertex {
    ...
    double x;
    double y;
};

然后,您可以通过执行诸如vertex1.x - vertex2.x等操作来对它们进行操作,这在我看来比vertex1[0] - vertex2[0]更易读。作为额外的奖励,它完全避免了您的异常问题。

好的,我希望它们都可以被访问。你可以使用vertex1.x()-vertex2.x()或vertex[0]-vertex2[0]。基本上,我想把数据放在一个数组中,并添加不同的访问方式。 - moka

1

处理超出边界的索引至少有两种选项,而不是使用异常:

  • 仅信任您的输入,记录使用超出边界的索引是未定义行为,并依赖您的调用者是专业人士[*]。
  • 如果索引超出边界,则通过调用 std::terminate() 或直接调用 abort() 等来中止操作,或许在打印错误消息后执行。

这两种方法之间有一种折衷方案,即使用 assert 宏。这将在发行版本(使用 NDEBUG 编译)中执行前者,在调试版本中执行后者。

并不是说异常一定是个坏主意,但它们有它们的问题。然而,如果您从不捕获它们,那么大多数问题都会消失。

在这种情况下,调用者必须向您传递0或1。如果他们有时传递2,并计划捕获当它们这样做时发生的异常,则可能没有希望。不要花太多时间担心这个问题。

另一个选择是接受所有输入,但将它们映射到其中一个值。例如,您可以对输入进行按位与运算。这使得您的代码非常简单,但明显的缺点是它会掩盖其他人的错误。

[*] 并不是说专业人士不会犯错。他们也会,只是不指望你来挽救他们的错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接