Saturday, February 16, 2013

qt-signal-tools - Pre-packaged slot calls and connecting signals to arbitrary functions in Qt 4

A useful new feature in Qt 5 is the ability to connect signals to arbitrary functions instead of just Qt signals/slots/properties, including C++11 lambdas.  As this page on the Qt Project wiki explains, this is especially useful when writing code to perform async operations where you often want to pass additional context to the slot.

I've written a small library for Qt 4 which provides similar functionality. The library includes:
  • QtCallback - A pre-canned QObject method call. QtCallback stores an object, a slot to call and optionally a list of pre-bound arguments to pass to the slot. This is useful if you need to pass additional context to the slot, other than the values provided by the signal.
  • QtSignalForwarder::connect() - Connects signals from QObjects to arbitrary functions or methods or QtCallbacks. You can use this together with bind() and function<> to pass additional arguments to the method other than those provided by the signal or re-arrange arguments. You can think of this as a more flexible alternative to the QSignalMapper class that Qt 4 provides. There are also a couple of utility features:
    • QtSignalForwarder::delayedCall() - A more flexible alternative to QTimer::singleShot() which can be used to invoke an arbitrary function after a set delay.
    • Event connections - Invoke an arbitrary function or QtCallback when an object receives a particular type of event. This is useful when the object does not have a built-in signal that is emitted in response to that event and requires less boilerplate than using QObject::installEventFilter()
  • safe_bind() - A downside of connecting a signal to a function object is that the signal does not automatically disconnect if the receiver is destroyed. safe_bind() creates a wrapper around a (QObject*, function) pair which when called, invokes the function if the object still exists or does nothing and returns a default value if the object has been destroyed. You can use this together with QtSignalForwarder to connect a signal to an arbitrary method on a QObject which effectively 'disconnects' when the receiver is destroyed.
For example usage, please see the README, the examples and the tests. The code is available from github.com/robertknight/qt-signal-tools.  The requirements are:
  • Qt 4.8
  • A compiler with the TR1 standard library extensions (most C++ compilers from the past few years - including MSVC >= 2008 and GCC 4.x. I have tested with MSVC 2010 and recent GCC/Clang versions) or one which supports equivalent features from the C++11 standard library.
Compared to the implementation in Qt 5, there are a few disadvantages:
  • Argument type checking happens at runtime when QtSignalForwarder::connect() is called, similar to standard QObject signal-slot connections. QObject::connect() in Qt 5 can do type checking at compile time.
  • In order to do the runtime type checking, the types of arguments passed from the signal to the function or method must be registered using Q_DECLARE_METATYPE() or qRegisterMetaType()
  • Using QtSignalForwarder does have additional overhead since a hidden proxy object is created to route the signal and arguments to the target function. I investigated using a single proxy object for all forwarded signals or a pool of proxies. Unfortunately it turns out that the QObject::sender() and QObject::senderSignalIndex() functions which are used internally have a cost that is linear in the number of connections.
Please let me know if you find this useful. If there is any other related functionality which you'd like to see, please let me know in the comments.