有什么建议吗?
编译器给你最好的建议。并非函数中的所有控制路径都包含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()
上面的函数一样),在大多数情况下,最好的办法是根本不处理异常,让它们向上传播调用堆栈。
引用戴夫亚伯拉罕的话:
通常处理异常的最好方法是根本不处理它们。如果您可以让它们通过您的代码并允许析构函数处理清理,那么您的代码会更干净。
为避免泄漏内存、资源或一般责任,请使用适当的RAII 包装器编写异常安全的代码。Jon Kalb 在这个由两部分组成的演讲中给出了这个意义上的优秀指导方针。
特别是,避免编写catch (...)
处理程序:发明异常是为了防止程序员忽略错误,并且在通用处理程序中将它们全部吞下而不重新抛出它们是忽略它们的最佳方法。
笔记:
请注意,您的实现Pop()
有点问题:如果T
在您已经修改堆栈指针之后,将元素返回给调用者时抛出的复制构造函数或移动构造函数会发生什么?
这就是 C++ 标准库定义两个独立函数的原因,pop()
并且top()
:因为它允许提供强有力的保证,即为您的pop()
操作提供事务语义 - 要么删除元素而不抛出异常,要么函数根本没有效果.