mr-edd.co.uk :: horsing around with the C++ programming language

sigfwd: forwarding Qt signals anywhere

[10th July 2010]

Just when I started feeling more comfortable with your templates, you mix them up with Qt internals!!

— long-suffering coworker

Though I have a number of reservations about the Qt platform, it's undeniable that it gets you a functional cross-platform GUI far quicker than doing it yourself. In fact I'd go so far as to say that some of the more recent releases of Qt have provided sufficient functionality on Mac OS X to allow our current project at work to ascend the far side of the UI uncanny valley. So the inner pragmatist easily quashes the inner perfectionist as far as choice of UI toolkit is concerned.

That won't stop me moaning about some of the stuff I don't like though, of course ;)

One of the biggest complaints often leveled against Qt is the way in which the signals and slots mechanism is implemented:

A better signals/slots mechanism is clearly possible nowadays, but I think perhaps that the level at which compilers implemented templates wasn't up to scratch when Qt's system was conceived.

In past projects using Qt, my coworkers and I have found that it tends to start trickling down in to the depths of your code, even if there are equivalent features in the standard library. So despite Qt4 being released a few years ago, we still haven't dared moved one of our products off of Qt3 — despite the fact Qt4 offers many useful features we could make good use of. There are just too many dependencies throughout that code base — some on Qt3 internals and behavioural quirks — to make the move.

For this reason, I've been pushing to keep Qt at bay in the UI code as far as possible in our latest project. We've gone for an MVC setup and we're forwarding all the Qt signals to boost signals something like this:

// header
class UI : public QMainWindow
{
    Q_OBJECT

    public:
        UI();

        boost::signals2::signal<void ()> OnClick;

    private slots:
        void ForwardClick();
};
// implementation
UI::UI()
{
   // setup widgets, etc
   // ...

   connect(button, SIGNAL(clicked()), this, SLOT(ForwardClick()));
}

void UI::ForwardClick()
{
   OnClick();
}

As I mentioned earlier, Qt signals require the receiver to inherit QObject. In our code, that would mean a Controller (from Model-View-Controller). By using the Boost Signals2 library instead, we can remove that coupling. But of course, we're coupled more to boost now, though we're all quite content for boost to be used in all levels of the code. We'd probably be stuffed without it, in fact :)

My biggest gripe with this system is that there's still a lot of boilerplate code needed to forward the signals. So I started looking for ways to reduce it and eventually came across this article that describes how to go about adding signals and slots to Qt's meta-back-end at run-time.

After a little work, I managed to re-write the code as follows:

// header
class UI : public QMainWindow
{
    public:
        UI();

        boost::signals2::signal<void ()> OnClick;
};
// implementation
UI::UI()
{
   // setup widgets, etc
   // ...

   Forward(button, SIGNAL(clicked()), OnClick);
}

That's a pretty good reduction in the amount of boilerplate (and it adds up when you've got multiple signals to forward). Also, the Q_OBJECT and slots macros are no longer needed which means the moc step of the build can be skipped.

The templatey C++ I wrote to implement this is a little bit heavy. Not too heavy though, I think. But as the opening quote of this post points out, there's more than one kind of complexity inherent in this kind of code. So I'm not sure whether or not we'll continue to use the technique or go back to explicit forwarding in the end.

A new library is born

I discovered later on, though, that I'm not the first person to want to do this kind of thing. But the existing solutions weren't as complete as what I had managed to cobble together, so I decided to re-write the code as an open source library and add some additional bells and whistles.

The result is sigfwd. Using the library, you can actually connect Qt signals to any C++ function or functor. It also makes a very decent conservative-but-optional attempt at verifying that the receiver's call signature is compatible with that of the Qt signal when the connections are made.

More information is available on the sigfwd page and wiki on bitbucket.

If you find a use for it, do let me know!

Comments

martinsm

[10/07/2010 at 22:36:22]

This is very nice indeed.
But it is not possible to skip "moc" step if you have your own new signals defined, right?

Edd

[10/07/2010 at 22:45:49]

That's true -- if you need to define new Qt signals, then you'll have to moc.

But presumably the new signals you'll define will effectively be some kind of forwarding or adaptation of existing ones? If that's the case, then since you can now connect existing Qt signals directly to e.g. boost::signals2::signals, you don't necessarily need to create new Qt signals.

But it's your call as to whether or not you want to go that far...

(optional)
(optional)
(required, hint)

Links can be added like [this one -> http://www.mr-edd.co.uk], to my homepage.
Phrases and blocks of code can be enclosed in {{{triple braces}}}.
Any HTML markup will be escaped.