Wednesday, 26 November 2008

Using Boost Bind and Boost Function with Qt

One very, very, very annoying feature of Qt in C++ is how very difficult it is to create bespoke slots.

With gtkmm, you can more or less get away with using boost::bind so long as the bind results in a 0-ary function (otherwise the signal/slots library for gtkmm is not compatible.)

With Qt, you have to write a new member function, or heaven forbid, a new class. This is too much typing for me. Ideally, what I would like to do is something like:


connect(some_button,SIGNAL(clicked()),
boost::bind(&Foo::bar,something,else,entirely));


Doing this with bare Qt, I'd have to:

  • Create a new slot function: Foo::bar_special_case

  • Add member variables for the variables else,entirely

  • Finally use it as: connect(some_button,SIGNAL(clicked(),something,SLOT(bar_special_case()))



After doing this for the 100 billionth time, I realized that there should be a better way to do this. After months of investigation, I have discovered that there is no better way to do this.

Just kidding.

The idea is to create a slot handler that accepts boost::function types:

struct SignalHandler0 : QObject
{
private:
Q_OBJECT
public:
SignalHandler0(QObject * parent,
boost::function<void(void)> const & f):
QObject(parent), // parent will delete this object when destructed
m_f(f) {}

public slots:
void
handleSignal()
{
try
{
m_f();
}
catch(...)
{
// Cannot throw exceptions from signals.
ASSERT_BUG_HERE
}
}
private:
boost::function<void(void)> m_f;
};


If I were to use this class directly, I would write:

connect(button,SIGNAL(clicked()),
// Note: Not a leak as button will delete the handler when destructed
new SignalHandler0(button,boost::bind(&Foo::bar,something,else,entirely)),
SLOT(handleSignal()));


That's pretty much it. Of course, this is a bit too verbose so I'd write a free function:

bool
connect(QObject * sender, const char * signal,
boost::function const & f)
{
return QObject::connect(sender,signal,
// Note: Not a leak as sender will delete the handler when destructed
new SignalHandler0(sender,f),SLOT(handleSignal()));
}


And use it as:::connect(button,SLOT(clicked()),boost::bind(&Foo::bar,something,else,entirely))

The obvious problem with this approach is if you add any overload of the free function + bind and you are in for some fun. I prototyped a solution for this based on the Boost.FunctionTypes library but at the moment, I don't need this (i.e., I am happy with 0-ary bind)

Another thing... Why does the aforementioned function types library not handle boost function and boost bind?

Odd.

PS: I'm not dead. I just smell funny.
PPS: Sorry for so long in between posts, I've been very busy (the good kind!)

5 comments:

Dolazy said...

Qt's code generation system has been made obsolete by the boost function/bind/signal libraries. I think they should just get rid of it and embrace boost in their next versions. That's probably not going to happen however.

Sohail Somani said...

Without moc, you lose all the reflection capabilities of Qt. I haven't really had much use for them myself but I have heard that the reflection in Qt is quite useful.

Anyway, if you can add the capability yourself (for example, like the above) does it really matter?

Also, I think boost signals was not thread-safe for the longest time.

Ken Wu said...

Hi, it is good for me. But after using it a while, I created a class template to handle signals with arguments. Here is the code:
"http://paste.lisp.org/+1VGE". It's handy, the only inconvenience is that I must explicitly specify the function signature in the template parameter of QtSignalHandlerS. I tried to provide a convenient function for deriving the template paramter for the user, but I failed. Do you have any idea on this?
Thanks.

Sohail Somani said...

Hi Ken,

Your solution certainly supports more arguments and in fact, I also needed this functionality so I have extended the original example.

However, I think your solution could be improved by using C++ dynamic_cast and more templates instead of mimicking what moc does.

I should post a follow-up to this post based on what I have learned since then.

Good luck!

Johan said...

Sohail,

please, please post that follow-up. I'm new to Qt and have just run into this problem. While searching the net, I think your solution seems like the best approach!

Best regards,