QBoundMethod

Bound method is usually defined as class method associated with an object (instance of the class). But what if we could extend this term a bit and bind methods not only to objects but to an arbitrary number of arguments as well? Let’s assume that if we have a method bound to less arguments than it requires we can call it providing that we supply the missing arguments. If it is bound to all the arguments it needs, we can call it right away. This sort of feature doesn’t seem really useful unless you are familiar with Qt’s signals and slots mechanism. QSignalMapper could be considered a very degenerate case of bound methods factory. It allows to bind single numeric, textual, QWidget* or QObject* argument to an object and acts as a proxy – slots bound to its mapped() signal are called with an extra argument based on the object calling its map() slot because of user action or programmatically generated event.

This class (QSignalMapper) collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal.

I designed QBoundMethod to be very much unlike QSignalMapper. The latter seemed to be focused on restrictions. QBoundMethod is all about being versatile. It is capable of binding up to 8 generic arguments or up to 7 arguments and use an extra argument supplied to the invoke() slot call. Most of the time this functionality is used to consume an argument emitted by a signal.

Ok, so let’s imagine we have a QBoundMethod class declared in the following way:

#ifndef QBOUNDMETHOD_H
#define QBOUNDMETHOD_H

#include <QObject>
#include <QVector>
#include <QGenericArgument>
#include <QMetaMethod>

class QBoundMethod: public QObject
{
	Q_OBJECT
public:
	QBoundMethod(QObject *parent, const char *methodSignature, QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());
	QBoundMethod(QObject *parent, QObject *receiver, const char *methodSignature, QGenericArgument val0 = QGenericArgument(), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument());
	// ~QBoundMethod();
public slots:
	void invoke();
	void invoke(int);
	void invoke(bool);
	void invoke(float);
	void invoke(double);
	void invoke(const QString&);
protected:
	void construct(QObject *receiver, const char *methodSignature, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9);
	void invoke(const QGenericArgument &arg);
protected:
	QObject *m_Object;
	QMetaMethod m_MetaMethod;
	QVector<QGenericArgument> m_Arguments;
	QVector<QVariant> m_ArgumentsData;
};

#endif // QBOUNDMETHOD_H

Now we can do all sorts of magic I mentioned above, e.g. instead of:

QSignalMapper *mapper = new QSignalMapper(this);
mapper->setMapping(someWidget, "Hello world!");
QObject::connect(mapper, SIGNAL(mapped(QString)), this, SLOT(showMessage(QString)));
QObject::connect(someWidget, SIGNAL(clicked()), mapper, SLOT(map()));

we could write:

QObject::connect(someWidget, SIGNAL(clicked()), new QBoundMethod(this, "showMessage(QString)", Q_ARG(QString, "Hello world!")), SLOT(invoke()));

Basically it does the same thing but the syntax is at the same time much nicer and more powerful. For example we can bind more than one parameter:

QObject::connect(someWidget, SIGNAL(clicked()), new QBoundMethod(this, "showMessage(int,QString)", Q_ARG(int, QMessageBox::Warning), Q_ARG(QString, "Hello world!")), SLOT(invoke()));

or we can attach bound methods to signals with parameters, effectively mixing both bound arguments and signal arguments like this:

QObject::connect(someWidget, SIGNAL(toggled(bool)), new QBoundMethod(this, "showMessage(int,QString,bool"), Q_ARG(int, QMessageBox::Information), Q_ARG(QString, "Hello world!")), SLOT(invoke(bool)));

Notice how we used SLOT(invoke(bool)) instead of SLOT(invoke()) this time. invoke(something) slots are there precisely for this purpose, so that signal arguments could be mixed with bound arguments.

QBoundMethod‘s life cycle is similar to any dynamically allocated object in Qt. Its ownership either remains with its creator or it is transferred to the parent object specified either during the creation as a constructor parameter or set later using setParent() method. It certainly takes more allocations than QSignalMapper does but the power it provides is well worth it, especially when it is applied to UI, where most of the time signals are bound only at creation time.

All in all, QBoundMethod can be considered either a much improved version of QSignalMapper or perhaps a poorer version of closures mechanism. One way or another it is still a very powerful and most importantly useful class. I found it to be a real time saver in many of my projects. I’m going to publish full source code and documentation soon.

Leave a Reply

Your email address will not be published. Required fields are marked *