有什么建议吗?
编译器会给你最好的建议。并非您的函数中的所有控制路径都包含return 语句,并且您的函数应该返回一个值。
如果抛出异常并将控制权转移到 catch 处理程序,则该处理程序将向 cerr 打印一些内容,然后在函数的末尾流出,而实际上 return 没有任何内容。
这是未定义的行为。根据 C++11 标准的第 6.6.3/2 段:
[..] 从函数末尾流出相当于没有值的返回; 这会导致未定义
值返回函数中的行为。
对于默认可构造值,您可以通过在函数结束前添加 return T() 语句来解决此问题:
template<typename T>
T Stack<T>::Pop()
{
try
{
// ...
}
catch (OutOfBoundsException&)
{
// ...
}
catch (...)
{
// ...
}
return T();
// ^^^^^^^^^^^
}
然而,更合理的方法是不让Pop() 吞下异常,而是重新抛出它。 Pop() 没有关于如何从这种情况下发生的错误中恢复的战略级信息:
template<typename T>
T Stack<T>::Pop()
{
try
{
// ...
}
catch (OutOfBoundsException&)
{
// ...
throw; // <== Re-throw after printing the diagnostic
}
catch (...)
{
// ...
throw; // <== Re-throw after printing the diagnostic
}
}
如果记录错误消息的责任根本不属于Pop(),那就更好了,因为在这个意义上Pop()可能应该被具有不同要求的代码重用(有些人可能不希望要记录任何内容,有些人可能希望将消息记录到文件中,有些人可能希望以不同的语言记录消息,等等)。
因此,您的函数的更合理版本实际上是:
template<typename T>
T Stack<T>::Pop()
{
if (m_index<0) throw OutOfBoundsException(m_index);
--m_index;
return(m_array[m_index]);
}
一般来说,您应该尝试(不是双关语)避免try/catch 块,除非您必须:
- 翻译异常
- 从错误中恢复(但您需要战略知识才能做到这一点)
如果这不是你的任务(就像上面 Pop() 这样的函数的情况),在大多数情况下,最好的办法是根本不处理异常并让它们向上传播调用堆栈。
引用Dave Abrahams:
通常处理异常的最佳方法是根本不处理它们。如果您可以让它们通过您的代码并允许析构函数处理清理,那么您的代码会更干净。
为避免泄漏内存、资源或一般责任,请使用适当的 RAII 包装器编写异常安全的代码。 this two-part talk by Jon Kalb 给出了这方面的优秀指南。
特别是,避免编写catch (...) 处理程序:发明异常是为了防止程序员忽略错误,将它们全部吞入通用处理程序而不重新抛出它们是忽略它们的最佳方法。
注意:
注意,Pop() 的实现有点问题:如果 T 的复制构造函数或移动构造函数在将元素返回给调用者时抛出,在你已经修改了堆栈指针之后会发生什么?
这就是 C++ 标准库定义两个独立函数 pop() 和 top() 的原因:因为它允许提供 强保证,即为您的 pop() 操作提供事务语义- 要么元素被移除而不抛出异常,要么函数完全没有效果。