简体   繁体   English

C ++异常处理准则

[英]Exceptions handling guidelines for C++

I understand benefits of using exceptions handling in C++ and I'm aware that it may be tricky. 我了解在C ++中使用异常处理的好处,并且我知道这可能很棘手。 One of rule says, that every function may throw. 一条规则说,每个函数都可能抛出。 Ok, but there are situations, where we want to make sure, that function does not throw. 好的,但是在某些情况下,我们需要确保该函数不会抛出。 I'm looking for any well-known practices or guidelines for handling such situations. 我正在寻找处理此类情况的任何知名做法或指南。 Examples: 例子:

try
{
    // do something
}
catch (std::runtime_error& error)
{
    save_log (error);
    emit_dbus_signal (error);
}

I don't care if save_log() or emit_dbus_signal() will fail, I only want to make sure, that I tried to call them. 我不在乎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 ();

I only want to make sure, that I tried to finalize thread_pool and socket_pool . 我只想确保确定尝试完成thread_poolsocket_pool Any error during finalization process should be handled inside finalize() methods. 在完成过程中的任何错误都应在finalize()方法中处理。

I can remember, which functions does not throw, but it will work only for small programs. 我记得,哪些功能不会抛出,但仅适用于小型程序。 Should I add suffix like _nothrow to such "non throwing" functions' names and handle this while writing code? 我是否应该在此类“非抛出”函数的名称后加上_nothrow类的后缀,并在编写代码时进行处理? Exceptions specification is deprecated since C++11 so I want to avoid it. 从C ++ 11开始不推荐使用异常规范,因此我想避免使用它。 What about noexcept ? noexcept呢? I still not sure if I understand this new feature. 我仍然不确定我是否了解此新功能。 Is it what I'm looking for? 是我要找的东西吗

There's no excpetions checking at compile time in C++11 right? 在C ++ 11中编译时没有任何检查,对吗?

Or maybe I'm completely wring? 还是我完全绞尽脑汁? :) :)

I think you need to understand RAII as a starting point. 我认为您需要了解RAII作为起点。

Once you're using RAII correctly, you'll find that most of the case where you thought you needed to manually finalize objects have magically disappeared: And, so in a lot of cases, that means you can dispense with the try/catch/finalize approach entirely. 正确使用RAII后,您会发现在大多数情况下,您认为需要手动完成对象的操作已神奇地消失了:而且,在很多情况下,这意味着您可以省去try / catch /完全确定方法。

As others have said, you're still going to want your save_log/emit_dbus_signal calls in a catch statement somewhere... 正如其他人所说,您仍然希望在某个地方的catch语句中调用save_log/emit_dbus_signal ...

In the above case, ThreadPool's constructor would call init(), and the destructor would call finalize(). 在上述情况下,ThreadPool的构造函数将调用init(),而析构函数将调用finalize()。

You should certainly document which functions are guaranteed not to throw, don't just "remember"! 当然,您应该记录保证不抛出哪些函数,而不仅仅是“记住”!

A common example is that swap functions should be no-throw. 一个常见的例子是swap函数应该是非抛出的。 In that case, there's no reason to put nothrow in the name, it's fairly fundamental that many uses of swap need to be nothrow. 在这种情况下,我们没有理由把nothrow的名称,这是相当根本的是许多用途swap必须抛出异常。 You could likewise make a rule for your project that log functions never throw. 您也可以为您的项目制定一条规则,即log功能永远不会抛出。 But this still needs to be documented. 但这仍然需要记录。 Ideally, every function should document what it throws and why, but failing that every function should certainly document what level of exception guarantee it offers, and "nothrow" is the strongest level. 理想情况下,每个函数都应记录其引发的原因以及原因,但如果不能肯定每个函数应记录其提供的异常保证级别,则“无”是最强的级别。

If I had throwing and non-throwing versions of the same functionality, then personally I'd put nothrow in the name to distinguish them. 如果我具有相同功能的投掷和非投掷版本,那么我个人会在名称中使用nothrow来区分它们。 Other than that, see what the code looks like. 除此之外,看看代码是什么样的。 It's possible you'll find yourself writing a piece of code in which you call seven functions in a row, all of which have to be nothrow for the code to be correct. 您可能会发现自己编写了一段代码,并在其中连续调用了七个函数,所有这些函数都必须保留,以确保代码正确。 It would probably be helpful to future readers not to have to go and check the declaration of each of those functions to make sure it really doesn't throw, although IDEs help with that. 这对将来的读者可能会有所帮助,而不必去检查每个函数的声明以确保它确实不会抛出,尽管IDE对此有所帮助。 They certainly don't want to have to read 7 doc files if that's avoidable. 如果可以避免的话,他们当然不希望读取7个doc文件。 In that case, I suppose it might be helpful to have Hungarian-style warts in the function names, but that kind of thing can rapidly get out of hand and make the code harder to read, not easier. 在那种情况下,我认为在函数名称中包含匈牙利风格的疣可能会有所帮助,但是这种事情很快就会失控,使代码更难阅读,而不是更容易。

Also, if you use a naming convention then operator overloads become rather difficult - you can't distinguish between a throwing and non-throwing operator+ by name. 另外,如果使用命名约定,则运算符重载将变得相当困难-您无法通过名称区分出throwing和non-throwing operator+

Empty exceptions specifications are OK, and C++11 noexcept is probably better. 空异常规范还可以,C ++ 11 noexcept可能更好。 Aside from their meaning to the compiler, they help with documentation. 除了对编译器的意义外,它们还提供文档帮助。 It's non-empty exception specifications that are troublesome. 麻烦的是非空的异常规范。

I agree with what everyone is saying about finalize : that's what destructors are for. 我同意每个人都对finalize所说的话:那就是析构函数的目的。

This depends on what you actually intend on doing. 这取决于您实际打算做什么。 When you say that you do not care whether save_log or emit_dbus_signal fails, you are not saying what don't care means. 当您说不在乎save_logemit_dbus_signal是否失败时,您并不是在说无关紧要的意思。 That is, if save_log fails, do you want to still try and emit_dbus_signal ? 也就是说,如果save_log失败,您是否仍要尝试使用emit_dbus_signal If so, you can: 如果是这样,您可以:

catch ( std::runtime_error& error ) {
   try { save_log( error ); } catch (...) {}
   try { emit_dbus_signal( error ); } catcn ( ... ) {}
}

If you do not care about emit_dbus_signal not being called if save_log fails, another approach would be enclosing the whole try/catch inside a second try/catch : 如果你不关心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();

There are actually other approaches, like using a RAII to ensure that threadpool.finalize() is called regardless of how the function completes in the lines of ScopeGuard . 实际上还有其他方法,例如使用RAII来确保调用threadpool.finalize() ,而不管该函数如何在ScopeGuard的行中完成

For the finalization functions, you should consider using wrapper classes with the RAII principle instead, that is, rewrite your code using: 对于终结函数,您应该考虑使用具有RAII原理的包装器类,即,使用以下代码重写代码:

try {
    initialized_thread_pool pool = thread_pool.init();
} catch (std::runtime_error& error) { handle(error); }

and do the finalization in the destructor of initialized_thread_pool. 并在initialized_thread_pool的析构函数中进行终结处理。

Once you get that principle right, exception specifications don't seem that much of an issue anymore. 一旦您掌握了正确的原则,异常规范就不再是问题。

Read up on RAII http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization 在RAII上阅读http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

If your function throws, any stack variables in that function will still have their destructors called. 如果函数抛出异常,则该函数中的所有堆栈变量仍将调用其析构函数。 That means you can simply call finalize() inside the ~ThreadPool() and ~SocketPool() destructors. 这意味着你可以简单地调用finalize()里面~ThreadPool()~SocketPool()析构函数。

If you can't modify the ThreadPool or SocketPool classes, you can write your own wrapper classes that call finalize on destruction, eg 如果您不能修改ThreadPool或SocketPool类,则可以编写自己的包装器类,这些类在销毁时调用finalize,例如

class ScopedThreadPool
{
public:
    ScopedThreadPool(ThreadPool &threadPool) : m_threadPool(threadPool) {}
    ~ScopedThreadPool() { m_threadPool.finalize(); }

private:
    ThreadPool &m_threadPool;
};

And then use it like this... 然后像这样使用它...

ThreadPool threadPool;
ScopedThreadPool scopedThreadPool(threadPool);

When the function exits (either via return or throw ) then the ScopedThreadPool destructor will call finalize() for you. 当函数退出时(通过returnthrow退出),那么ScopedThreadPool析构函数将为您调用finalize()

Add throw() to the end of a function declaration. throw()添加到函数声明的末尾。 The compiler (gcc at least) will then complain if the function throws exceptions. 然后,如果函数抛出异常,则编译器(至少为gcc)会抱怨。

void function() throw();

This is standard C++ that says this function doesn't throw any exceptions. 这是标准的C ++,表示此函数不会引发任何异常。 Previously you could say which exceptions it threw, but I think C++11 removed that feature. 以前您可以说它引发了哪些异常,但是我认为C ++ 11删除了该功能。 Only the empty clause, as above, remains. 如上所述,仅空子句保留。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM