Signals/Slots behavior review



  • Qt Signals/Slots has the following behavior (correct me if anything wrong)
    Behavior A: Meta object system will convert the parameter of signals from "Const Object &" to "Object" ;
    Behavior B: Meta object system will NOT convert the parameter of signals from "enum type" to "int" ;

    Is that a good design? I doubted.

    @#include <QtCore/QCoreApplication>
    #include <iostream>
    #include <QDebug>
    using namespace std;

    class MyApp : public QObject
    {
    Q_OBJECT

    enum MyEnumType {
          MyTypeItem = 1,
    };
    

    signals:
    void discovery2(const QString&);
    void discovery3(MyApp::MyEnumType);
    };

    class MyQObject : public QObject
    {
    Q_OBJECT
    private slots:
    void b_slot() {};
    void discovery2(QString) {};
    void discovery3(int) {};
    };

    int main(int argc, char *argv[])
    {
    QCoreApplication a(argc, argv);

    MyApp app;
    MyQObject obj;
    
    bool ok2 = QObject::connect(&app;, SIGNAL(discovery2(QString)),
                               &obj;, SLOT(discovery2(QString)));
    bool ok3 = QObject::connect(&app;, SIGNAL(discovery3(MyApp::MyEnumType)),
                               &obj;, SLOT(discovery3(int)));
    qDebug() << ok2 << endl;
    qDebug() << ok3 << endl;
    
    return a.exec(&#41;;
    

    }

    #include "main.moc"
    @

    Can you guess the output ? The answer is.....

    ##################
    QObject::connect: Incompatible sender/receiver arguments
    MyApp::discovery3(MyApp::MyEnumType) --> MyQObject::discovery3(int)
    true
    false
    ##################



  • The hint here is what Moc did for SIGNAL macro:

    @static const char qt_meta_stringdata_MyApp[] = {
    "MyApp\0\0discovery2(QString)\0"
    "discovery3(MyApp::MyEnumType)\0"
    };@


  • Moderators

    Thanks for picking up this issue. I was a little puzzled by the reported "Behaviour A" yesterday in "this thread ":http://developer.qt.nokia.com/forums/viewthread/7732/ .

    What compiler and OS did you use?



  • Both of the following tool chains can get the above results:

    1. Microsoft Visual C++ Compiler 9.0(x86) and Windows XP.
    2. Mingw as a GCC for Windows targets and Windows XP.

    [quote author="koahnig" date="1310635394"]Thanks for picking up this issue. I was a little puzzled by the reported "Behaviour A" yesterday in "this thread ":http://developer.qt.nokia.com/forums/viewthread/7732/ .

    What compiler and OS did you use? [/quote]



  • I think this is intended, and even though behaviour B can cause you some inconvenience, I think it's probably a good idea from an object oriented perspective. I cannot see any problems or inconvenciences with behaviour A.



  • For an explanation of behaviour A, have a look at the "QMetaObject::normalizedSignature()":http://doc.qt.nokia.com/latest/qmetaobject.html#normalizedSignature function.


  • Moderators

    Hi Ludde,

    I do not complay with this statement yet.
    [quote author="ludde" date="1310636723"]I cannot see any problems or inconvenciences with behaviour A.[/quote]

    One part is if the result of an action is causing problems in general. I am not sure, but this may also depend on the compiler architecture. However, I agree that it is probably not an issue for properly designed compilers.

    The second part is that engineers are used to design their programs for certain behaviours. So in some cases if you like to have a deep copy, you know the consequences and implications. In general it may not be a problem to substitute "meth ( object )" with "meth ( const object & )". To my understanding is also part of some compiler optimization (hopefully, only when it save). With a synchronuous call to the method is mostly not an issue. But I assume the behaviour shall change for async connections. This is unconvenient.

    However, what happens for sync call to method, but another thread is changing the content of your object?

    So, it might be intentionally, but it worries me a bit. Hopefully, you or someone else can shade some more light on this.



  • Why should the meta object system behave different?

    A normalized signature has to provide a short nonambiguous identification of a member for lookup, not for invocation and is not a function signature.

    You are not allowed to overload methods whose parameters only differ in constness and reference.
    @
    class Foo
    {
    void bar(const QString&); // ambiguous
    void bar(QString); // ambiguous
    }
    @
    So for the meta object systems normalized signature it doesn't matter if it is const& or not because you are not allowed to specify both which would prevent an unique identification.

    If you take a look at the meta object code you will see that the actual call is an ordinary function call where the compiler takes care of any constness or references. So a const& method call is still a const& method call and a non-const value call is still a non-const value call.

    In addition the type of an enum Foobar is Foobar, not enum nor int. The underlying storage (which might be actually an integer) is implementation dependent. This is one of the reason why compilers usually do not allow an implicit conversion from int to enums.

    So I see no way the meta object system shoud - or even can - behave different.


  • Moderators

    Lukas, thanks for clarification of these details.
    As I wrote already in general it is not really a reason to worry. The automatic substitution with a const & saves copying time and potentially memory.

    The answer for async "(QueuedConnection)":http://doc.qt.nokia.com/latest/qt.html#ConnectionType-enum can be here. There is a short note in this section: [quote] With queued connections, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes. [/quote]

    [quote author="koahnig" date="1310638705"]However, what happens for sync call to method, but another thread is changing the content of your object?
    [/quote]

    This worry boils down to a general issue when sharing memory between different threads then.



  • Thank you all, specially Lukas.

    Lukas, could you please explain more for the following comments? I did not understand it fully:

    bq. If you take a look at the meta object code you will see that the actual call is an ordinary function call where the compiler takes care of any constness or references. So a const& method call is still a const& method call and a non-const value call is still a non-const value call.



  • For the meta object system to work properly every meta object enabled class has to go through the meta object compiler, moc. This tool adds some members to those classes, including a method qt_metacall() responsible for calling methods by their method index.

    For a simple class like
    @
    class Foo : public QObject
    {
    Q_OBJECT

    public slots:
    void reference(QString& parameter);
    void constReference(const QString& parameter);
    void value(QString parameter);
    };
    @
    this method will look like
    @
    int Foo::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
    {
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
    return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
    switch (_id) {
    case 0: reference((reinterpret_cast< QString()>(_a[1]))); break;
    case 1: constReference((reinterpret_cast< const QString()>(_a[1]))); break;
    case 2: value((reinterpret_cast< QString()>(_a[1]))); break;
    default: ;
    }
    _id -= 3;
    }
    return _id;
    }
    @
    As calling methods through their indices is cumbersome, Qt automatically binds names to method indices (not to the methods themselves). This is done via another member added by the meta object compiler
    @
    static const char qt_meta_stringdata_Foo[] = {
    "Foo\0\0parameter\0reference(QString&)\0"
    "constReference(QString)\0value(QString)\0"
    };
    @
    These names have to fulfill some conditions:

    • Names have to be unique, as every name should map to a single method index. In C this would be the function name; in C++ - where it is allowed to have multiple methods with the same name - you will have to add the parameter types to distinct various different overloaded methods. However, overloaded methods are not allowed to distinct only in the arguments access type (call by value / call by reference) and restriction (const and non-const) - so this information is not needed for generating an unique method identifier (see the code example in the last post).
    • Names have to be short. Comparing numbers is fast, comparing strings (usually) slow. Every time a method call is requested through the method's name the qt_meta_stringdata table has to be combed through to find the method index. This is (one of) the reason(s) method names are normalized, which means removing all whitespaces and unneccessary identifiers and restrictions (like const) which are not required to generate an unique name.
    • Names have to be uniform. "value(QString parameter)" is not the same as "value (QString)" as "value(QString)". Although all of this names refer to the same method, only the last one represents a valid method name as it is the only one listed in qt_meta_stringdata. This is the reason you should always use the SIGNAL macro, the SLOT macro and/or normalizedSignature().
    • Names have to be meaningful. Keep in mind that the qt_meta_stringdata table could map any name to a method index - the normalized signatures have been chosen because it is the most reasonable choice.

    If you now take a look at the normalized signature of Foo::constReference (snippet #3, line 3) you will see that the const and reference identifiers have been removed (because they are not neccessary to generate a unique name) - although the parameter is still passed as const (snippet #2, line 9) when this method is invoked using the method index. And even though not quite visible in the code snippets references are handled correctly too.
    @
    ...

    void Foo::reference(QString& parameter)
    {
        parameter = "reference";
    }
    
    void Foo::constReference(const QString& parameter)
    {
        // parameter = "constReference";   // not allowed
    }
    
    void Foo::value(QString parameter)
    {
        parameter = "value";
    }
    
    ...
    
    Foo bar;
    
    QString stringD;
    QString stringE;
    QString stringF;
    
    QMetaObject::invokeMethod(&bar;, "reference", Q_ARG(QString&, stringD));
    QMetaObject::invokeMethod(&bar;, "constReference", Q_ARG(QString, stringE));
    QMetaObject::invokeMethod(&bar;, "value", Q_ARG(QString, stringF));
    
    // those semicolons after &bar; are added by the forum engine and should not be there
    
    qDebug() << stringD << stringE << stringF;   // "reference" "" ""
    

    @

    I'm still not quite sure if this is now more understandable ;-) But I hope this helps to understand why signal and slot names have not that much impact on method invocation and it is absolutely legit if they differ in parameter specifications.

    And please keep in mind that this is just a very simplified explanation of the meta object system! I just can empathic recommend to download the Qt sources and dig into them. Most of the stuff you will see there is quite interesting - even quite impressive in places - and really informative and instructive.



  • Lukas, thanks again. Your stuff is really good enough to create a wiki saving there.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.