Results 1 to 2 of 2

Thread: QBoundMethod

  1. #1
    Join Date
    Aug 2010
    Posts
    5
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows Symbian S60 Maemo/MeeGo

    Lightbulb 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:
    Qt Code:
    1. #ifndef QBOUNDMETHOD_H
    2. #define QBOUNDMETHOD_H
    3.  
    4. #include <QObject>
    5. #include <QVector>
    6. #include <QGenericArgument>
    7. #include <QMetaMethod>
    8.  
    9. class QBoundMethod: public QObject
    10. {
    11. Q_OBJECT
    12. public:
    13. // ~QBoundMethod();
    14. public slots:
    15. void invoke();
    16. void invoke(int);
    17. void invoke(bool);
    18. void invoke(float);
    19. void invoke(double);
    20. void invoke(const QString&);
    21. protected:
    22. 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);
    23. void invoke(const QGenericArgument &arg);
    24. protected:
    25. QObject *m_Object;
    26. QMetaMethod m_MetaMethod;
    27. QVector<QGenericArgument> m_Arguments;
    28. QVector<QVariant> m_ArgumentsData;
    29. };
    30.  
    31. #endif // QBOUNDMETHOD_H
    To copy to clipboard, switch view to plain text mode 
    Now we can do all sorts of magic I mentioned above, e.g. instead of:
    Qt Code:
    1. QSignalMapper *mapper = new QSignalMapper(this);
    2. mapper->setMapping(someWidget, "Hello world!");
    3. QObject::connect(mapper, SIGNAL(mapped(QString)), this, SLOT(showMessage(QString)));
    4. QObject::connect(someWidget, SIGNAL(clicked()), mapper, SLOT(map()));
    To copy to clipboard, switch view to plain text mode 
    we could write:
    Qt Code:
    1. QObject::connect(someWidget, SIGNAL(clicked()), new QBoundMethod(this, "showMessage(QString)", Q_ARG(QString, "Hello world!")), SLOT(invoke()));
    To copy to clipboard, switch view to plain text mode 
    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:
    Qt Code:
    1. QObject::connect(someWidget, SIGNAL(clicked()), new QBoundMethod(this, "showMessage(int,QString)", Q_ARG(int, QMessageBox::Warning), Q_ARG(QString, "Hello world!")), SLOT(invoke()));
    To copy to clipboard, switch view to plain text mode 
    or we can attach bound methods to signals with parameters, effectively mixing both bound arguments and signal arguments like this:
    Qt Code:
    1. 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)));
    To copy to clipboard, switch view to plain text mode 
    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.

    Watch my blog for the second part of this article: http://adaszewski.fachowcy.pl

  2. #2
    Join Date
    Aug 2010
    Posts
    5
    Qt products
    Qt4
    Platforms
    MacOS X Unix/X11 Windows Symbian S60 Maemo/MeeGo

    Default Re: QBoundMethod

    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.

    Qt Code:
    1. #include "qboundmethod.h"
    2.  
    3. #include <QMetaObject>
    4.  
    5. 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)
    6. {
    7. m_Object = receiver;
    8. const QMetaObject *metaObj = m_Object->metaObject();
    9. for (int i = 0; i < metaObj->methodCount(); i++)
    10. {
    11. const char *sig = metaObj->method(i).signature();
    12. if (QString(sig) == methodSignature)
    13. {
    14. m_MetaMethod = metaObj->method(i);
    15. break;
    16. }
    17. }
    18. m_ArgumentsData.reserve(10);
    19. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val0.name()), val0.data()));
    20. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val1.name()), val1.data()));
    21. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val2.name()), val2.data()));
    22. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val3.name()), val3.data()));
    23. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val4.name()), val4.data()));
    24. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val5.name()), val5.data()));
    25. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val6.name()), val6.data()));
    26. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val7.name()), val7.data()));
    27. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val8.name()), val8.data()));
    28. m_ArgumentsData.push_back(QVariant(QVariant::nameToType(val9.name()), val9.data()));
    29. m_Arguments.reserve(10);
    30. m_Arguments.push_back(QGenericArgument(val0.name(), &m_ArgumentsData[0].data_ptr().data));
    31. m_Arguments.push_back(QGenericArgument(val1.name(), &m_ArgumentsData[1].data_ptr().data));
    32. m_Arguments.push_back(QGenericArgument(val2.name(), &m_ArgumentsData[2].data_ptr().data));
    33. m_Arguments.push_back(QGenericArgument(val3.name(), &m_ArgumentsData[3].data_ptr().data));
    34. m_Arguments.push_back(QGenericArgument(val4.name(), &m_ArgumentsData[4].data_ptr().data));
    35. m_Arguments.push_back(QGenericArgument(val5.name(), &m_ArgumentsData[5].data_ptr().data));
    36. m_Arguments.push_back(QGenericArgument(val6.name(), &m_ArgumentsData[6].data_ptr().data));
    37. m_Arguments.push_back(QGenericArgument(val7.name(), &m_ArgumentsData[7].data_ptr().data));
    38. m_Arguments.push_back(QGenericArgument(val8.name(), &m_ArgumentsData[8].data_ptr().data));
    39. m_Arguments.push_back(QGenericArgument(val9.name(), &m_ArgumentsData[9].data_ptr().data));
    40. }
    To copy to clipboard, switch view to plain text mode 
    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.
    Qt Code:
    1. 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):
    2. QObject(parent)
    3. {
    4. construct(parent, methodSignature, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
    5. }
    6.  
    7. 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):
    8. QObject(parent)
    9. {
    10. construct(receiver, methodSignature, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
    11. }
    To copy to clipboard, switch view to plain text mode 
    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.
    Qt Code:
    1. void QBoundMethod::invoke(const QGenericArgument &arg)
    2. {
    3. QVector<QGenericArgument> tmp = m_Arguments;
    4. for (int i = 0; i < tmp.count(); i++)
    5. {
    6. if (tmp[i].name() == 0)
    7. {
    8. tmp[i] = arg; // Q_ARG(int, param);
    9. break;
    10. }
    11. }
    12. 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]);
    13. }
    To copy to clipboard, switch view to plain text mode 
    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.
    Qt Code:
    1. void QBoundMethod::invoke()
    2. {
    3. 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]);
    4. }
    To copy to clipboard, switch view to plain text mode 
    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.
    Qt Code:
    1. void QBoundMethod::invoke(int param)
    2. {
    3. invoke(Q_ARG(int, param));
    4. }
    5.  
    6. void QBoundMethod::invoke(bool param)
    7. {
    8. invoke(Q_ARG(bool, param));
    9. }
    10.  
    11. void QBoundMethod::invoke(float param)
    12. {
    13. invoke(Q_ARG(float, param));
    14. }
    15.  
    16. void QBoundMethod::invoke(double param)
    17. {
    18. invoke(Q_ARG(double, param));
    19. }
    20.  
    21. void QBoundMethod::invoke(const QString &param)
    22. {
    23. invoke(Q_ARG(QString, param));
    24. }
    To copy to clipboard, switch view to plain text mode 
    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 and visit my blog: http://adaszewski.fachowcy.pl

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.