Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stack object Qt signal and parameter as reference

May I have a "dangling reference" with the following code (in an eventual slot connected to the myQtSignal)?

class Test : public QObject {     Q_OBJECT  signals:     void myQtSignal(const FooObject& obj);  public:     void sendSignal(const FooObject& fooStackObject)     {         emit  myQtSignal(fooStackObject);     } };  void f() {     FooObject fooStackObject;     Test t;     t.sendSignal(fooStackObject); }  int main() {     f();     std::cin.ignore();     return 0; } 

Particularly if emit and slot are not executed in the same thread.

like image 947
Guillaume Paris Avatar asked Dec 10 '11 10:12

Guillaume Paris


People also ask

Does Qt have special handling for const reference parameters?

Hence in the absence of a wrapper that would store a copy (or a shared pointer), a queued slot connection could wind up using the bad data. But it was raised to my attention by @BenjaminT and @cgmb that Qt actually does have special handling for const reference parameters.

How do I get the sender of a signal in Qt?

For cases where you may require information on the sender of the signal, Qt provides the QObject::sender () function, which returns a pointer to the object that sent the signal. Lambda expressions are a convenient way to pass custom arguments to a slot: It is possible to use Qt with a 3rd party signal/slot mechanism.

Is it dangerous to pass a reference to a Qt signal?

Passing a reference to a Qt signal is not dangerous thanks to the way signal/slot connections work: If the connection is direct, connected slots are directly called directly, e.g. when emit MySignal(my_string) returns all directly connected slots have been executed.

Is it possible to pass objects by value in Qt?

Really you should be passing objects by value over signal and slot connections. Note that Qt has support for "implicitly shared types", so passing things like a QImage "by value" won't make a copy unless someone writes to the value they receive: http://qt-project.org/doc/qt-5/implicit-sharing.html


1 Answers

UPDATE 20-APR-2015

Originally I believed that passing a reference to a stack-allocated object would be equivalent to passing the address of that object. Hence in the absence of a wrapper that would store a copy (or a shared pointer), a queued slot connection could wind up using the bad data.

But it was raised to my attention by @BenjaminT and @cgmb that Qt actually does have special handling for const reference parameters. It will call the copy constructor and stow away the copied object to use for the slot calls. Even if the original object you passed has been destroyed by the time the slot runs, the references that the slots get will be to different objects entirely.

You can read @cgmb's answer for the mechanical details. But here's a quick test:

#include <iostream> #include <QCoreApplication> #include <QDebug> #include <QTimer>  class Param { public:     Param () {}     Param (Param const &) {         std::cout << "Calling Copy Constructor\n";     } };  class Test : public QObject {     Q_OBJECT  public:     Test () {         for (int index = 0; index < 3; index++)             connect(this, &Test::transmit, this, &Test::receive,                 Qt::QueuedConnection);     }      void run() {         Param p;         std::cout << "transmitting with " << &p << " as parameter\n";         emit transmit(p);         QTimer::singleShot(200, qApp, &QCoreApplication::quit);     }  signals:     void transmit(Param const & p); public slots:     void receive(Param const & p) {         std::cout << "receive called with " << &p << " as parameter\n";     } }; 

...and a main:

#include <QCoreApplication> #include <QTimer>  #include "param.h"  int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);      // name "Param" must match type name for references to work (?)     qRegisterMetaType<Param>("Param");       Test t;      QTimer::singleShot(200, qApp, QCoreApplication::quit);     return a.exec(); } 

Running this demonstrates that for each of the 3 slot connections, a separate copy of the Param is made via the copy constructor:

Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor receive called with 0x1bbf7c0 as parameter receive called with 0x1bbf8a0 as parameter receive called with 0x1bbfa00 as parameter 

You might wonder what good it does to "pass by reference" if Qt is just going to make copies anyway. However, it doesn't always make the copy...it depends on the connection type. If you change to Qt::DirectConnection, it doesn't make any copies:

transmitting with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter receive called with 0x7ffebf241147 as parameter 

And if you switched to passing by value, you'd actually get a more intermediate copies, especially in the Qt::QueuedConnection case:

Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor Calling Copy Constructor receive called with 0x7fff15146ecf as parameter Calling Copy Constructor receive called with 0x7fff15146ecf as parameter Calling Copy Constructor receive called with 0x7fff15146ecf as parameter 

But passing by pointer doesn't do any special magic. So it has the problems mentioned in the original answer, which I'll keep below. But it has turned out that reference handling is just a different beast.

ORIGINAL ANSWER

Yes, this can be dangerous if your program is multithreaded. And it's generally poor style even if not. Really you should be passing objects by value over signal and slot connections.

Note that Qt has support for "implicitly shared types", so passing things like a QImage "by value" won't make a copy unless someone writes to the value they receive:

http://qt-project.org/doc/qt-5/implicit-sharing.html

The problem isn't fundamentally anything to do with signals and slots. C++ has all kinds of ways that objects might be deleted while they're referenced somewhere, or even if some of their code is running in the call stack. You can get into this trouble easily in any code where you don't have control over the code and use proper synchronization. Techniques like using QSharedPointer can help.

There are a couple of additional helpful things Qt offers to more gracefully handle deletion scenarios. If there's an object you want to destroy but you are aware that it might be in use at the moment, you can use the QObject::deleteLater() method:

http://qt-project.org/doc/qt-5/qobject.html#deleteLater

That's come in handy for me a couple of times. Another useful thing is the QObject::destroyed() signal:

http://qt-project.org/doc/qt-5/qobject.html#destroyed

like image 174
HostileFork says dont trust SE Avatar answered Sep 17 '22 21:09

HostileFork says dont trust SE