Today, I’d like to discuss the implementation of QBoundMethod in every detail. The core of all functionality is placed in QBoundMethod::construct() method. It is responsible for creating a copy of generic arguments provided as its parameters. For every QGenericArgument, a QVariant holding copy of the original data is created and appended to a list of QVariants. Then for every QVariant on the list, a new QGenericArgument is created holding pointer to the copy of the original data in the QVariant. Sounds a bit complicated and in fact it could be achieved in a more direct manner but I figured that using Qt classes is nicer after all.
#include "qboundmethod.h" #include <QMetaObject> void QBoundMethod::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) { m_Object = receiver; const QMetaObject *metaObj = m_Object->metaObject(); for (int i = 0; i < metaObj->methodCount(); i++) { const char *sig = metaObj->method(i).signature(); if (QString(sig) == methodSignature) { m_MetaMethod = metaObj->method(i); break; } } m_ArgumentsData.reserve(10); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val0.name()), val0.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val1.name()), val1.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val2.name()), val2.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val3.name()), val3.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val4.name()), val4.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val5.name()), val5.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val6.name()), val6.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val7.name()), val7.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val8.name()), val8.data())); m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val9.name()), val9.data())); m_Arguments.reserve(10); m_Arguments.push_back(QGenericArgument(val0.name(), &m_ArgumentsData[0].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val1.name(), &m_ArgumentsData[1].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val2.name(), &m_ArgumentsData[2].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val3.name(), &m_ArgumentsData[3].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val4.name(), &m_ArgumentsData[4].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val5.name(), &m_ArgumentsData[5].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val6.name(), &m_ArgumentsData[6].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val7.name(), &m_ArgumentsData[7].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val8.name(), &m_ArgumentsData[8].data_ptr().data)); m_Arguments.push_back(QGenericArgument(val9.name(), &m_ArgumentsData[9].data_ptr().data)); }
The constructor methods just call QBoundMethod::construct(). Two variants of constructor methods are available. First one assumes that parent object is also the object holding the method which will become bound method. The other one allows parent object and receiver object to be specified separately. The name receiver comes from the fact that bound method actually has to be a slot, and since objects with slots in this type of situation are called receivers in Qt nomenclature I decided to name it this way.
QBoundMethod::QBoundMethod(QObject *parent, const char *methodSignature, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9): QObject(parent) { construct(parent, methodSignature, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); } QBoundMethod::QBoundMethod(QObject *parent, 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): QObject(parent) { construct(receiver, methodSignature, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); }
Now, another vital method is QBoundMethod::invoke(const QGenericArgument&). It creates a copy of list of QGenericArguments created in QBoundMethod::construct() and supplements it with another argument provided as a parameter to invoke() call. This way bound method can be called with all the bound arguments plus one another, dynamic argument. You will see below why this is important.
void QBoundMethod::invoke(const QGenericArgument &arg) { QVector<QGenericArgument> tmp = m_Arguments; for (int i = 0; i < tmp.count(); i++) { if (tmp[i].name() == 0) { tmp[i] = arg; // Q_ARG(int, param); break; } } m_MetaMethod.invoke(m_Object, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9]); }
QBoundMethod::invoke() is a simpler version of QBoundMethod::invoke(const QGenericArgument&). It doesn’t take any parameters and it calls the bound method only with the arguments bound during its construction.
void QBoundMethod::invoke() { m_MetaMethod.invoke(m_Object, m_Arguments[0], m_Arguments[1], m_Arguments[2], m_Arguments[3], m_Arguments[4], m_Arguments[5], m_Arguments[6], m_Arguments[7], m_Arguments[8], m_Arguments[9]); }
Now you will see how QBoundMethod::invoke(const QGenericArgument&) is used. It is wrapped in many QBoundMethod::invoke(something) calls where something includes types like int, float, bool, double and const QString&. If you’re familiar with Qt Framework, you must have noticed that these are the types usually emitted by all sorts of signals. The use for such code is obvious – these flavors of invoke() will be used as slots for such signals.
void QBoundMethod::invoke(int param) { invoke(Q_ARG(int, param)); } void QBoundMethod::invoke(bool param) { invoke(Q_ARG(bool, param)); } void QBoundMethod::invoke(float param) { invoke(Q_ARG(float, param)); } void QBoundMethod::invoke(double param) { invoke(Q_ARG(double, param)); } void QBoundMethod::invoke(const QString ¶m) { invoke(Q_ARG(QString, param)); }
I’m a little a bit surprised that it went so smoothly, yet it’s true. This is all there is to QBoundMethod. Hope it doesn’t seem too complex after all the explaining. I say, you just give it a try and I bet you’re gonna love it 😉
I’m going to write a third part of this article containing some useful examples and an archive with all the code. Stay tuned 🙂