我理解在C ++中使用异常处理的好处,我知道它可能很棘手。规则之一说,每个功能都可能抛出。好的,但有些情况,我们要确保,该功能不会抛出。我正在寻找处理此类情况的任何众所周知的做法或指南。例子:
try
{
// do something
}
catch (std::runtime_error& error)
{
save_log (error);
emit_dbus_signal (error);
}
我不在乎save_log()
或emit_dbus_signal()
会失败,我只想确定,我试图打电话给他们。
ThreadPool thread_pool;
SocketsPool socket_pool;
MainLoop main_loop;
try
{
thread_pool.init ();
socket_pool.init ();
main_loop.run ();
}
catch (std::runtime_error& error)
{
save_log (error);
emit_dbus_signal (error);
}
thread_pool.finalize ();
socket_pool.finalize ();
我只想确保,我试图最终确定thread_pool
和socket_pool
。最终确定过程中的任何错误都应在finalize()
方法中处理。
我记得,哪些函数没有抛出,但它只适用于小程序。我应该在这样的“非投掷”函数名称中添加_nothrow
这样的后缀,并在编写代码时处理这个吗?自C ++ 11以来,不推荐使用异常规范,因此我想避免使用它。那么noexcept
呢?我仍然不确定我是否理解这个新功能。这是我在找什么?
在C ++ 11中编译时没有检查是否正确?
或许我完全拧干了? :)
答案 0 :(得分:3)
我认为您需要了解RAII作为起点。
一旦你正确使用RAII,你会发现大多数你认为你需要手动完成对象的情况已经神奇地消失了:而且,在很多情况下,这意味着你可以免除尝试/ catch / finalize方法完全。
正如其他人所说的那样,你仍然希望在某个地方找到你的save_log/emit_dbus_signal
电话......
在上面的例子中,ThreadPool的构造函数将调用init(),而析构函数将调用finalize()。
答案 1 :(得分:3)
你当然应该记录哪些功能不会丢失,不要只是“记住”!
一个常见的例子是swap
函数应该是无抛出的。在这种情况下,没有理由将nothrow
放在名称中,因此swap
的许多用法需要不被提及,这是相当基本的。您也可以为log
函数永远不会抛出的项目制定规则。但这仍需要记录在案。理想情况下,每个函数都应该记录它抛出的内容和原因,但是失败的是每个函数都应该记录它提供的异常级别,并且“nothrow”是最强的级别。
如果我有相同功能的投掷和非投掷版本,那么我个人会在名称中添加nothrow
来区分它们。除此之外,看看代码是什么样的。您可能会发现自己正在编写一段代码,您可以在其中连续调用七个函数,所有这些代码都必须是代码才能正确。未来的读者可能会有所帮助,不必去检查每个函数的声明,以确保它真的不会抛出,尽管IDE有助于此。他们当然不希望阅读7个doc文件,如果这是可以避免的。在这种情况下,我认为在函数名称中使用匈牙利式疣可能会有所帮助,但是这种事情很快就会失控并使代码难以阅读,而不是更容易。
此外,如果您使用命名约定,那么运算符重载变得相当困难 - 您无法通过名称区分投掷和非投掷operator+
。
空异常规范没问题,而C ++ 11 noexcept
可能更好。除了它们对编译器的意义外,它们还有助于编写文档。这是非空的异常规范很麻烦。
我同意每个人对finalize
所说的话:这就是析构者的用途。
答案 2 :(得分:1)
这取决于你实际打算做什么。当您说您不关心save_log
或emit_dbus_signal
是否失败时,您并不是说不关心意味着什么。也就是说,如果save_log
失败,您是否仍想尝试emit_dbus_signal
?如果是这样,你可以:
catch ( std::runtime_error& error ) {
try { save_log( error ); } catch (...) {}
try { emit_dbus_signal( error ); } catcn ( ... ) {}
}
如果emit_dbus_signal
失败,如果您不关心save_log
,则另一种方法是将整个try/catch
括在第二个try/catch
内:
try {
try {
// current code
} catch ( std::runtime_error const & error ) {
// current handling
}
} catch (...) {} // ensure that no other exception escapes either the try or the catch blocks
thread_pool.finalize();
socket_pool.finalize();
实际上还有其他一些方法,比如使用RAII来确保调用threadpool.finalize()
,而不管函数在ScopeGuard的行中如何完成。
答案 3 :(得分:0)
对于终结函数,您应该考虑使用具有RAII原则的包装类,即使用以下代码重写代码:
try {
initialized_thread_pool pool = thread_pool.init();
} catch (std::runtime_error& error) { handle(error); }
并在initialized_thread_pool的析构函数中完成。
一旦你的原则正确,异常规范就不再那么重要了。
答案 4 :(得分:0)
阅读RAII http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
如果你的函数抛出,那个函数中的任何堆栈变量仍然会调用它们的析构函数。这意味着您只需在finalize()
和~ThreadPool()
析构函数中调用~SocketPool()
即可。
如果你不能修改ThreadPool或SocketPool类,你可以编写自己的包装类,在破坏时调用finalize,例如。
class ScopedThreadPool
{
public:
ScopedThreadPool(ThreadPool &threadPool) : m_threadPool(threadPool) {}
~ScopedThreadPool() { m_threadPool.finalize(); }
private:
ThreadPool &m_threadPool;
};
然后像这样使用它......
ThreadPool threadPool;
ScopedThreadPool scopedThreadPool(threadPool);
当功能退出时(通过return
或throw
),ScopedThreadPool
析构函数会为您调用finalize()
。
答案 5 :(得分:0)
将throw()
添加到函数声明的末尾。如果函数抛出异常,编译器(至少gcc)会抱怨。
void function() throw();
这是标准的C ++,它说这个函数不会抛出任何异常。以前您可以说它抛出了哪些例外,但我认为C ++ 11删除了该功能。如上所述,只有空子句仍然存在。