0

If I send a signal with a reference to an object, do I have any sort of guarantee that the slots will handle the signal before the object is subjected to changes by other parts of the program?

Or do I have to send a deep copy of the object with the signal to be sure?

user3050215
  • 185
  • 1
  • 5
  • 14
  • When you send the reference to an object with signal, it is transferred as a copy anyways: http://qt-project.org/doc/qt-4.8/qmetaobject.html#normalizedSignature . – vahancho May 12 '14 at 12:49
  • You should consider using [Qt implicit data sharing](http://qt-project.org/doc/qt-5/implicit-sharing.html), like Qt containers, `QString` and many other Qt data classes do. That works well together with Qt signals&slots. It may not be worth it for your use case, but it's good to know it's possible and relatively easy. – hyde May 12 '14 at 13:23
  • @vahancho "When you send the reference to an object with signal, it is transferred as a copy anyways" **No, it's not**. At least not when the resolved connection type is direct. – Kuba hasn't forgotten Monica May 12 '14 at 13:42
  • @hyde "You should consider using Qt implicit data sharing, like Qt containers" While this is otherwise reasonable advice, it doesn't really address the question of what guarantees there are - and there, indeed, are some :) – Kuba hasn't forgotten Monica May 12 '14 at 13:43
  • @KubaOber Well, if I add "...and pass the implicitly shared objects as values", then it does guarantee, that passed object is not subject to any changes by other parts of the program. – hyde May 12 '14 at 13:50

1 Answers1

3

The following holds: By the time the signal returns:

  1. All directly connected slots have been invoked.

  2. All blocking-queued connected slots have been invoked.

  3. All of the target objects with queued-connected slots have the relevant QMetaCallEvent posted to them.

Thus, you have the full guarantee you seek if your other code allows it, and if the connection is:

  • direct,
  • blocking-queued, or
  • automatic with the target object in the current thread.

With those connection types, the slots are all invoked before the the signal method returns. Recall that signals are otherwise ordinary C++ methods with their implementation generated by moc.

Also recall that the automatic connection types are resolved at the time the signal is emitted. If the target object is in the current thread, a direct connection is used, otherwise a queued connection results.

Of course, all those guarantees assume that none of your slots modify the object! Don't forget that merely passing the object by const reference is not enough - after all, some objects may be able to access your object directly. The design of your code must address the guarantees that you seek.

Your guarantee necessarily can't hold if the connection is:

  • queued, or
  • automatic with the target object in thread other than the current thread.

Passing data by reference to directly connected slots does not copy the instances:

//main.cpp
#include <QDebug>

class Data {
   int m_i;
public:
   Data(const Data & o) : m_i(o.m_i) {
      qDebug() << "copy constructed";
   }
   Data(int i = 0) : m_i(i) {
      qDebug() << "constructed";
   }
   Data(Data && o) : m_i(o.m_i) {
      qDebug() << "move constructed";
   }
   ~Data() {
      qDebug() << "destructed";
   }
   int i() const { return m_i; }
};
Q_DECLARE_METATYPE(Data)

QDebug operator<<(QDebug dbg, const Data & d)
{
   dbg << "Data:" << d.i();
   return dbg;
}

class Object : public QObject {
   Q_OBJECT
public:
   Q_SIGNAL void source(const Data &);
   Q_SLOT void sink(const Data & d) {
      qDebug() << "sinking" << d;
   }
};

int main()
{
   qDebug() << QT_VERSION_STR;
   Object o;
   o.connect(&o, SIGNAL(source(Data)), SLOT(sink(Data)));
   emit o.source(Data(2));
   return 0;
}

#include "main.moc"

Output:

4.8.6 
constructed 
sinking Data: 2 
destructed 

5.2.2 
constructed 
sinking Data: 2 
destructed 
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Alright, perfect! So the 'signal' is just replaced by a long list of function calls when compiled then. – user3050215 May 21 '14 at 09:34
  • @user3050215 A signal is not replaced by anything, it's a regular C++ method call. A signal is a regular method that, for direct connections, essentially iterates over a connected function pointer list and does indirect function calls. With a cherry on top, of course :) – Kuba hasn't forgotten Monica May 21 '14 at 13:41