This problem has been long solved and has an idiomatic solution in Qt.
All slot calls ultimately originate either from:
An event handler in an object is always reached through QCoreApplication::notify
. So, all you have to do is to subclass the application class and reimplement the notify method.
This does affect all signal-slot calls that originate from event handlers. Specifically:
all signals and their directly attached slots that originated from event handlers
This adds a per-event cost, not a per-signal cost, and not per-slot cost. Why is the difference important? Many controls emit multiple signals per a single event. An QPushButton
, reacting to a QMouseEvent
, can emit clicked(bool)
, pressed()
or released()
, and toggled(bool)
, all from the same event. In spite of multiple signals being emitted, notify
was called only once.
all queued slot calls and method invocations
They are implemented by dispatching a QMetaCallEvent
to the receiver object. The call is executed by QObject::event
. Since event delivery is involved, notify
is used. The cost is per-call-invocation (thus it is per-slot). This cost can be easily mitigated, if desired (see implementation).
If you're emitting a signal not from an event handler - say, from inside of your main
function, and the slot is directly connected, then this method of handling things obviously won't work, you have to wrap the signal emission in a try/catch block.
Since QCoreApplication::notify
is called for each and every delivered event, the only overhead of this method is the cost of the try/catch block and the base implementation's method call. The latter is small.
The former can be mitigated by only wrapping the notification on marked objects. This would need to be done at no cost to the object size, and without involving a lookup in an auxiliary data structure. Any of those extra costs would exceed the cost of a try/catch block with no thrown exception.
The "mark" would need to come from the object itself. There's a possibility there: QObject::d_ptr->unused
. Alas, this is not so, since that member is not initialized in the object's constructor, so we can't depend on it being zeroed out. A solution using such a mark would require a small change to Qt proper (addition of unused = 0;
line to QObjectPrivate::QObjectPrivate
).
Code:
template <typename BaseApp> class SafeNotifyApp : public BaseApp {
bool m_wrapMetaCalls;
public:
SafeNotifyApp(int & argc, char ** argv) :
BaseApp(argc, argv), m_wrapMetaCalls(false) {}
void setWrapMetaCalls(bool w) { m_wrapMetaCalls = w; }
bool doesWrapMetaCalls() const { return m_wrapMetaCalls; }
bool notify(QObject * receiver, QEvent * e) Q_DECL_OVERRIDE {
if (! m_wrapMetaCalls && e->type() == QEvent::MetaCall) {
// This test is presumed to have a lower cost than the try-catch
return BaseApp::notify(receiver, e);
}
try {
return BaseApp::notify(receiver, e);
}
catch (const std::bad_alloc&) {
// do something clever
}
}
};
int main(int argc, char ** argv) {
SafeNotifyApp<QApplication> a(argc, argv);
...
}
Note that I completely ignore whether it makes any sense, in any particular situation, to handle std::bad_alloc
. Merely handling it does not equal exception safety.