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!)