Exception handling with Qt signal/slot pattern
-
Lately I'm having a closer look to exception handling with "modern" C++ (C++11, 14, ...).
In the C++ Core Guidelines (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#e-error-handling) there are clear indications that exception handling should be the way to go for error handling.
But when looking at Qt I find https://doc.qt.io/qt-5/exceptionsafety.html#exceptions-in-client-code noting:
"Throwing an exception from a slot invoked by Qt's signal-slot connection mechanism is considered undefined behaviour, unless it is handled within the slot"Testing this with a simple GUI App (a QButton with two slots connected to clicked()) made clear, that in the standard source pattern it is not easy to detect which slot has thrown an exception. So catching an excpetion outside the slot is difficult. And it might get even more complex, if the called slot is a queued connection.
I'm thinking about adding a Q_DECL_NOEXCEPT (resolved to "noexcept" in most modern compilers) to each slot.
With this I could semantiacally indicate, that this slot must not let escape any exception. During maintenance of a method (in the .cpp files you can't see, that it is a slot) the maintainer has a chance to see, that exception handling has to be done inside this (slot) method.
Maybe more sofisticated compiler / offline tools might have a chance to detect if a slot is not "noexcept" and could give a hint to the maintainer of the method, what possible exception is not handled.On the ohter hand, with the "new" connect pattern (introduced with Qt 5) we can connect to any method, there seems to be no need have the possible "slots" in a "public/protected/private slots:" section anymore. So each member function could be a slot, leading to a "noexcept" to each member method ... ?
Do you have a nice pattern how to design QObject derived classes that are using singal/slot and still use excpetion handling as the error mehod in part where the actual work is done?
Would be interested to see your way ... -
@moellney
Hi. Not sure what you are asking here. It doesn't matter whether some "C++ Core Guidelines" states that "exception handling should be the way to go". Qt was designed years ago, it does not raise exceptions. And as it states, and you have discovered, you must not let a Qt slot raise an exception to the outside world. Nor from any other places in Qt, e.g. an event or an override you write.Only your own code raises exceptions, since Qt does not. Yes, you must wrap any such inside
try ... catch ...
to prevent an exception going back to anything Qt. If you want to addnoexcept
to your methods that's fine. It is true that, for example, any method can be used as a slot; you do not have to put them inside aslots:
, though most people do for clarity. You only need to do thenoexcept
inside e.g. methods you will actually use as a slot etc.In a word, I do not think there is a foolproof way in C++ or Qt code to verify that a method used as a slot does have
noexcept
and/or does not raise an exception. -
@JonB
Thanks for the quick respone with some more re-affirming details.Well, what I'm asking for is a supporting design pattern - foolproof would be asking for to much :-)
Ideas / guides like:
- No exception throwing in any class, that is derived by QObject. True work should be done in "worker" classes (not derived by QObject), that should do error handling by exceptions.
- Some kind of conceptual border: signal/slot event handling based on Qt without exception handling, work and housekeeping with exception handling?
And there is a class QException (https://doc.qt.io/qt-5/qexception.html) to support throwing exception accross threads... have to look for some examples.
-
In the C++ Core Guidelines there are clear indications that exception handling should be the way to go for error handling.
That's just the opinion of the authors. Don't let others dictate what is good for you ;) I personally dislike exceptions very much and don't use them unless I'm forced to e.g. when a std function throws. Even then I put them in a tiny inline try/catch wrapper functions. I also don't use libraries that don't internalize exception handling. Throwing across a library boundary is just bad design IMO. Exception handling has a performance cost and to me it makes code harder to read (they're basically goto's that call destructors).
Do you have a nice pattern how to design QObject derived classes that are using signal/slot and still use excpetion handling as the error method
I would simply say don't. Qt is designed around error handling without exceptions and it's a good idea to code in the style of the library you're using. Otherwise you're just running uphill where you don't need to. Why bother? If you really really really want exceptions just don't let them escape your methods. Simple as that.
As for throwing exceptions across threads - well, that's just my opinion again, but it is for people who like pain and love to spend long hours debugging. Why do that to yourself in the first place?
On the topic of slots and new connect syntax. Slot is a method registered as such in the meta system. This is done by marking them with the slots macro. You can query a meta object to list slots. In new syntax connection can be made to any functor - slot, method, free function, lambda or a struct with () operator. Making a connection to a method doesn't make that method a slot.
noexcept
is one of those things I wish C++ had a different default for. My code doesn't use exceptions, so I should go through thousands of classes in my code base made over the last 20 years and mark all their methods as noexcept? That's just unrealistic. Theoretically compiler can make some optimization for noexcept methods, but I chose to live without that. If profiling shows a bottleneck in particular place I'll check if noexcept helps, but honestly I largely ignore existence of exceptions in the language. -
I agree with @JonB and @Chris-Kawa
I used to use exceptions just because, in books and tutorials, they say you have to.
Then arrived frameworks like Qt or Cocoa on Mac, that don't use them at all, disturbing in the first time !
Then you realize that exceptions are cumbersome to use and not needed most of the time.
But, there're some kind of processing that would be difficult not to used them, typically: syntactic parsers. If errors arrived deep in the parsing process, throwing an exception is the easy way to manage them.I wrote an expressions parser in pure C++ that use exceptions but with no interaction with the UI or any framework specific classes at all. That way, the parser is seamlessly portable from one framework to another (from Cocoa to Qt in my case)
<Digression> I discover that the adjective "syntaxic" doesn't exist in english, but "syntactic" do instead, strange :)
-
@Chris-Kawa
Thanks for your comment on the topic.
I see, as you point out, that "Qt is designed around error handling without exceptions".I know - from different conference talks and papers - that there is quite a community keeping away from exceptions. There are even contexts where exceptions are not allowed as the response time is not clear.
However, my application environment allows to have undefined response times for exceptions.
And arguments shown by talks you can find on http://www.exceptionsafecode.com/ convince me to use exceptions as error/exception handling method.Anyway, my intention for this discussion was not to start a discussion about pro/contra of exceptions, though.
My question was more, if there is an established design concept for applications to seperate the Qt (no exception support) part and a exception aware working model / backend / ...ps:
I'm still looking into details about the STL and benefits you get from e.g. noexcept move constructors. E.g. in the STL implementation of MS (github) you can read in the push_back method (submethods):if (_Whereptr == _Mylast) { // at back, provide strong guarantee if constexpr (is_nothrow_move_constructible_v<_Ty> || !is_copy_constructible_v<_Ty>) { _Uninitialized_move(_Myfirst, _Mylast, _Newvec, _Al); } else { _Uninitialized_copy(_Myfirst, _Mylast, _Newvec, _Al); } } else { // provide basic guarantee _Uninitialized_move(_Myfirst, _Whereptr, _Newvec, _Al); _Constructed_first = _Newvec; _Uninitialized_move(_Whereptr, _Mylast, _Newvec + _Whereoff + 1, _Al); }
And I'm longing for this strong / basic guarantees / invariants in my code to have it stable and still readable....
-
@moellney said:
Anyway, my intention for this discussion was not to start a discussion about pro/contra of exceptions, though.
Yup, sorry. One of the downsides of exceptions is it's almost impossible to talk about them without talking about whether they're worth it.
My question was more, if there is an established design concept
Well, as the link you originally posted says - exceptions thrown across connections are undefined behavior. This in practice means that anything that can be used in a Qt connection should not throw exceptions. This means any method of a QObject derived class, any free standing function, lambda or functor you intend to use in a connection. So yes, you should mark all of that
noexcept
and generally the more localized your exception handling is the better/safer. As for threading - exception handling is only supported by QtConcurrent and QException derived exception types. Anything else is unspecified, so basically a no-no.
If you build models/backend around exceptions don't just keep them local and make sure you catch them before crossing into the Qt land.I'm still looking into details about the STL
Yeah, this is one of the reasons I wish noexcept had different defaults :( Implementations like this make things slow by default, which is very sad for C++.