Can someone explain how the new qt5 signals and slots work



  • I was wondering if someone could explain how the new qt5 signals and slots work I am new to the old ones, let alone the new ones. I know you have to have a signal and a slot, but they have even removed the keywords in the new version, and it doesn't seem so easy to read. I have read the documentation, but reading and understanding are two different things.

    For instance I have this

    QObject::connect(modelBox, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),this,SLOT(updateBoxList(const QModelIndex & , const QModelIndex &)));
    

    And the new format is this.

    QObject::connect(modelBox, &QSlider::valueChanged,this, &QProgressBar::setValue);
    

    What I don't understand is where is the signal, and where is the slot.
    Why has QSlider replaced the Signal keyword, and there is no SLOT keyword. It is hard for me to grasp the concept of this new design.

    I know what a QSlider, and I know what QProgressBar does, but I don't know how to add my dataChanged part and then call my function.

    Don't get me wrong my code is working, but I am trying to get my head around the new syntax.

    Any help would be appreciated. I have only been using QT for just over a week, so I am very new.



  • @Asimov Well first off you don't have to use the new format. In many cases I still use SIGNAL() and SLOT() format.

    The new format has it's pros and cons. One of the very big pros is the ability to use a lambda for the slot. So you can do something like:

    connect(obj, &MyObject:mySignal, [=]() { doSomethingHere(); });

    That eliminates the need for defining slots when doing something quick, like say printing a message, updating a label, changing a counter, etc. It can all be done inline with a lambda.

    Here is an article on the differences between old and new method https://wiki.qt.io/New_Signal_Slot_Syntax.

    That should explain it all to you. :)


  • Moderators

    @Asimov said in Can someone explain how the new qt5 signals and slots work:

    QObject::connect(modelBox, &QSlider::valueChanged,this, &QProgressBar::setValue);

    In your example &QSlider::valueChanged is the pointer to the signal and &QProgressBar::setValue is the pointer to the slot, so the order is the same as in the old connect.
    See here for more information: https://wiki.qt.io/New_Signal_Slot_Syntax



  • @ambershark @Asimov

    an additional note to lambdas.

    You could potentially do something like this:

    QLabel *lbl_1 = new QLabel();
    lbl_1->setText("I'm a Label");
    connect(sender, &Sender::updateLabels, [=](const QString &newValue) {
      lbl_1->setText(newValue);
    });
    

    and it works!

    But,if you write it like this and your lbl_1 is for whatever reason deleted, your app will crash the next time updateLabels is called.
    However, since 5.2 there is an overload which adds a "context object". When that object is destroyed, the connection is broken.
    I haven't seen this documented well enough yet, maybe someone can point me in the right direction.

    You can simply change the connect statement to this:

    connect(sender, &Sender::updateLabels, lbl_1,[=](const QString &newValue) {
      lbl_1->setText(newValue);
    });
    

    and the connection will be removed, whenn lbl_1 ceases to exist.



  • @J.Hilk Could you not just add a check in your lambda, something like if (lbl_1) lbl_1->setText(newValue); and when you clean up lbl_1 set it to nullptr.

    I honestly haven't played with the new style signals and slots much. Out of many years of habit I tend to use the SIGNAL() and SLOT() macros unless I specifically need a lambda. So I haven't run into these crash scenarios yet.



  • @ambershark
    yes of course you can do it that way, In fact before I found the overload context object that is how I handled this kind of situations.

    But the problem is, the connection still existes and takes up a cycle of the cpu. Doesn't seem like much, but if one is not careful, it can explode pretty quickly.

    There's also the possibility of a manuel disconnect:

    QMetaObject::Connection m_connection;
    //…
    m_connection = QObject::connect(…);
    //…
    QObject::disconnect(m_connection);
    

    But that can mean lots of new member variables or at least many more lines of code.

    The best solution, is in deed - in my opinion - to pass the refered context object to the QObject::Connect:

    connect(sender, &Sender::updateLabels, lbl_1,[=](const QString &newValue) {
    lbl_1->setText(newValue);
    });

    and let Qt handle the potential disconnect internally.



  • @J.Hilk Yea that sounds like the preferred way for me too. I would much rather have Qt handle it. I don't want to save connection variables and deal with disconnecting them later.

    And I definitely don't want to have all my signals build up and need to be checked even after they are no longer relevant. That is definitely a waste of CPU cycles and it all adds up. It will never be as slow as Java though. ;P



  • @Asimov You seem to have too different connections - different signals and slots - in your example. The structure of both is

    sender_object, signal, receiver_object, slot

    In the old syntax you need to mark the signal and slot with macro markers. With the new one they are not needed, but you have to give the signal and slot in form of

    the_address_of_the_member_function

    which is

    a_pointer_to_the_member_function

    which is expressed as

    &class_name::function_name

    I don't know how much you know low-level C/C++ stuff. If you know how pointers to functions work you understand this. The member functions of a class reside somewhere in the memory. Each function is common to all objects of that class, as is all static data. Only the normal data members are per-object. So basically what you need to call a member function of an object is the object and the pointer to the member function. And you can get the pointer through the class. Therefore you use both the object name and the class name in the new syntax.

    On important aspect of the new syntax is that it checks compile time that the signal and slot are compatible. I sometimes have run in a situation where in the old syntax I got the arguments wrong and the connection didn't work but it doesn't give any error. In the case where there are no overloads I find the new syntax short and clear and easy to type after you have been accustomed to the syntax.

    In some cases you may run into problems if you don't know all the details. See for example QAbstractSocket::error.

    Note: Signal error is overloaded in this class. To connect to this one using the function pointer syntax, you must specify the signal type in a static cast, as shown in this example:
    
    connect(abstractSocket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
        [=](QAbstractSocket::SocketError socketError){ /* ... */ });
    

    Unfortunately it wasn't documented in a place where I tried to look at (the signal/slot documentation) but the compiler gave errors which I didn't understand. The old syntax worked. No need to say how ugly and difficult this new syntax with static_cast is...



  • @Eeli-K
    a quick addition to that with qt5.7 we canshorten overload /static_cast drastically:

    //static_cast of overloaded functions:
    QObject::connect(
            a,
            static_cast<void(MyObject::*)(int)>(&MyObject::signal),
            b,
            static_cast<void(MyObject::*)(int)>(&MyObject::slot));
    
    // Qt 5.7 + allows qOverload, but it requires C++14 to be enabled:
        QObject::connect(
            a,
            qOverload<int>(&MyObject::signal),
            b,
            qOverload<int>(&MyObject::slot));
    
    // slightly longer, but works in C++11:
        QObject::connect(
            a,
            QOverload<int>::of(&MyObject::signal),
            b,
            QOverload<int>::of(&MyObject::slot));
    

    Example taken from here:



  • @Eeli-K
    I know a little bit about pointers a * denotes a pointer to a variable, and an & is the address of the variable at it's simplest term.

    The reason I want to learn the new format is that I am worried they will get rid of the old format which I have only just learnt.

    @Everyone
    If a signal is not marked with a Macro how do I do the datachanged. It is for a QTableView so would I do something like &QTableView::dataChanged or something?

    I haven't read all these posts properly yet, but going to have a good read now.

    Thanks for all the replies. I am not new to C++ but I have got a little rusty because I haven't done much for a few years, while 3D Modelling.
    I am however new to QT.

    PS. I just tried QTableView::dataChanged and it doesn't work LOL.
    PPS. I don't know what Lambda means.



  • @Asimov http://en.cppreference.com/w/cpp/language/lambda.

    Show us how you did the dataChanged signal.



  • @Asimov Yes, & takes the address of a variable. In this case is takes the address of a function. You can store the address to a variable which is a pointer to a function. In C++11, which is required anyways by the new signal/slot syntax, you can do

    auto sgnl = &QSlider::valueChanged;
    auto slt = &QProgressBar::setValue;
    QObject::connect(modelBox, sgnl, this, slt);
    

    sgnl and slt are pointers to functions (signals are also functions). I don't even try to find out their types explicitly - that's where the auto keyword is very handy - but you can see some details in http://www.cprogramming.com/tutorial/function-pointers.html. (See also "Related articles" there.)

    You don't have to be afraid of them getting rid of the old syntax. Maybe after 10 or 15 years, but they don't want to break existing working code and can't (yet) get rid of the MOC (Meta Object Compiler which creates C++ code out of Q_OBJECT, signals:, slots: etc.) even if they wanted to. You can read https://woboq.com/blog/reflection-in-cpp-and-qt-moc.html and https://woboq.com/blog/verdigris-qt-without-moc.html for some in-depth discussion about MOC.



  • @ambershark
    I didn't have to do this signal, it is part of the QAbstractItemModel, and I am using it on a QStandardItemModel in a QTableView
    ie
    http://doc.qt.io/qt-4.8/qabstractitemmodel.html#dataChanged

    My signal and slot are working fine. I just wanted to know how to do it the qt5 way.
    PS. I have already read that article, but found it as clear as mud LOL.

    @Eeli-K
    It's nice to know they are keeping the old system for a while, but I would like to understand the new system.
    @jsulm
    The second was just an example, and I kinda understand how that works, but I don't understand how to use my datachange example in the new format.

    PS. Just found out that Lambda's are like little functions with no name. Not heard of these before.
    PPS.
    Just tried this. I think I am close

    QObject::connect(modelBox,&QAbstractItemModel::dataChanged(const QModelIndex &),this,&DbManager::updateBoxList(const QModelIndex &));
    

    But it is giving me an error at the end. I don't think &DbManager is right. It is the name of my class which updateBoxList is in.



  • @Asimov Well, there is only

    [signal] void QAbstractItemModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ())
    

    in QAbstractItemModel, so &QAbstractItemModel::dataChanged(const QModelIndex &) doesn't work. Don't keep teasing us, give us the error message. Have you tried

    QObject::connect(modelBox,&QAbstractItemModel::dataChanged,this,&DbManager::updateBoxList);
    

    ? The niceness of this is that if neither signal nor slot are overloaded you should be able to leave off the arguments because the function names are unambiguous.

    If you really want to understand the new system, look at https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html. But it's quite techical, too.



  • @Eeli-K
    Hey that worked, it called my function. That wasn't as bad as I thought.
    I knew I was close, just couldn't get the syntax.

    I wonder if there is any way to send the table index to my function?

    But the good news is that my function is being called.

    PS. I managed to get the index like this

    void DbManager::updateBoxList(const QModelIndex &index){
        qDebug() << "index: " << index.row();
    }
    

    This is must better than the original signal and slots I originally used.



  • @Asimov You already get the indexes with that call you are making to connect. Just so you know with that updateBoxList call you are only getting the topLeft index, so if you ever update more than 1 index, you don't handle that case... for that you would need:

    void DbManager::updateBoxList(const QModelIndex &topLeft, const QModelIndex &buttomRight)
    {
    }
    


  • Yes ambershark &topleft gives me what my index was giving me. Really I think all I was doing was renaming &topleft to index if you think about it. Thanks. So the new format isn't so bad after all, just takes a bit of getting used to.
    Thanks to everyone who answered as well. Been a great help.

    BTW I keep looking at your avatar and thinking firefox, and then I look again and it looks like a fish.



  • @Asimov Lol yea it's a shark... an amber one. ;) My company logo. I never thought about it but it does kind of share a flow with firefox. The whole circular animal logo thing.

    Yea you were just renaming topLeft to index which is fine I was just pointing out that if you don't use topleft and bottomright and have multiple index changes than your "index" will not be what you expect it to be. If you control updates to always be a single index you'll be fine though.



  • I know I marked this as solved, but I just wanted you to know I am still learning. I also had another couple of other signals and slots which used the old format, but seeing as they were still working I didn't change it straight away, and now I have managed to change it to the new format without any problems.

    Old Format

    connect(searchButton, SIGNAL(clicked()),this,SLOT(on_searchButton_clicked()));
    connect(lineEdit, SIGNAL(returnPressed()),this,SLOT(on_searchButton_clicked()));
    

    And re-written in new format

    connect(searchButton,&QPushButton::clicked,this,&MainWindow::on_searchButton_clicked);
    connect(lineEdit,&QLineEdit::returnPressed,this,&MainWindow::on_searchButton_clicked);
    

    I probably only scratched the surface with signals and slots, but I am progressing slowly. Thanks all.


  • Qt Champions 2016

    @Asimov said in Can someone explain how the new qt5 signals and slots work:

    I probably only scratched the surface with signals and slots, but I am progressing slowly. Thanks all.

    Probably not. If you understand the old syntax and understand how pointer-to-member (and/or function pointers) work then you know it all. The new Qt syntax is nothing else but the old syntax modified to be a handy wrap around the pointer-to-member syntax C++ provides.

    For completeness, a pointer to member is a function pointer, and a function pointer is a variable that stores the address of a function. Functions reside in the static part of the program (as Eeli K already noted). So you can get the address of that piece of memory. Incidentally the address of the function is exactly the function name in C++ syntax. :)
    You can use function pointers to call functions, naturally, consider this:

    void myCoolFunction(int x)
    {
    }
    
    void someOtherFunction
    {
        void (*functionPointerVariable)(int) = myCoolFunction; // or &myCoolFunction
        // Now functionPointerVariable points to the address of myCoolFunction and we can call the function through that ponter
        functionPointerVariable(10); // Calls myCoolFunction, equivalent to myCoolFunction(10)
    }
    

    Extending this to classes is somewhat trivial. The main thing to know is that (non-static) methods are the same as regular function, i.e. they reside in the static part of the program memory BUT they need an object to operate on. So one can get a pointer to a method in the same way, only the call is a bit different as you need to bind that function pointer to an object. Rewriting the above examples with member pointers is straightforward:

    class A
    {
        void someMethod(int)
        {
        }
    };
    
    void someOtherFunction()
    {
        void (A::*methodPointer)(int) = &A::someMethod; // methodPointer points to the function someMethod of the class A now
        A myObject; // We need an object to call the method, so let's create it.
    
        // Finally we can call the method of myObject with our function pointer. Notice the parentheses around the .* operator, they are required for this because of operator priorities.
        (myObject.*methodPointer)(10); // Equivalent to myObject.someMethod(10);
    }
    

    The whole point of this all is that you can store the pointer-to-member variable without actually specifying an object until the very last moment - when the call is done. And this is what Qt does, it stores the pointers and binds the object (with the .* or ->* operator only when the actual call is made.


Log in to reply