PDA

View Full Version : Questions about signals/slots and QObject lifetimes



themolecule
23rd May 2014, 15:11
I'm trying to find documentation about the deep nature of Signals and Slots (which are well documented in the source)...

for instance, connect() is frequently called from 'this', but if 'this' calls:


connect(that, SIGNAL(_something()), other, SLOT(_else()));

then is the connection connected to the lifetime of 'this', or is it attached to an internal database so that deleting 'this' will allow the connection to persist?

I'm hoping that deleting 'this' will destroy the connection, but 'this' is separate from 'that' and 'other', so the question is, are connections connected to the creator or the objects?

anda_skoa
23rd May 2014, 15:42
If you look at the signature of the connect method you used here, you will see that it is a static method of QObject.

So the "this" object is not involved at all with this connection.

The connection will persist as long as its sender and receiver exist or it is undone using QObject::disconnect().

Cheers,
_

d_stranz
23rd May 2014, 16:54
If you look at the signature of the connect method you used here, you will see that it is a static method of QObject.


Well, yes, but the OP's question really boils down to: if one of the instances involved in the connection (either the sender or the receiver) is destroyed, is the connection also destroyed?

The answer to that is "yes". If that was not the case, things would likely be crashing all over the place due to slots being invoked with dangling, deleted pointers.

QObject has a QObject::destroyed() signal which you can use to detect when an object you are interested in has been (or is about to be) destroyed. For example, if you keep a pointer to some QObject instance in a data structure, and that QObject's lifetime was controlled by something else, you'd want to make sure that pointer was still valid before using it. If you connect to the instance's destroyed() signal, you can be notified when it goes out of scope and can take appropriate action (like set your stored pointer variable to NULL).

themolecule
23rd May 2014, 17:44
You are both correct, thank you.

I failed to remember that connect() is a static member of QObject, which is, basically, this-less when called, so of course it makes sense that 'this' can be deleted without regard for 'that' or 'other' as far as connections go.

And both 'that' and 'other' remain consistent upon disconnect() or delete (probably because of slots), so if I want the lifetime of connect() to be connected to 'this', I need listeners on both sides.

Thanks!

Added after 14 minutes:

For anyone else interested:




connect(that, SIGNAL(_something()), other, SLOT(_else()));
connect(that, SIGNAL( destroyed(), this, SLOT(disconnection()) );
connect(other, SIGNAL( destroyed(), this, SLOT(disconnection()) );

//...

void someClass::disconnection()
{
//someBaseClass *o = dynamic_cast<someBaseClass *>(sender()); //not needed
//disconnect(that, SIGNAL(/*xyz*/), other, SLOT(/*tuv*/)); //not needed

this->deleteLater(); //the whole point
}

Lesiok
23rd May 2014, 18:31
RTFM (read this fine manual). From QObject destructor doc : All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue.

themolecule
23rd May 2014, 19:04
Wow man... the Fine Manual does not describe connections between objects when created from another object, which is a distinctly different question.

I think you may consider Reading The Fine Manual A Little More Closely in relation to the question, and as well read the responses.

It's a basic thing... offer something constructive that adds solution to the problem, or at least offer context to how the solution you offer makes sense, to separate your copy/paste from plain RTFM... please!

anda_skoa
23rd May 2014, 20:15
connect(that, SIGNAL(_something()), other, SLOT(_else()));
connect(that, SIGNAL( destroyed(), this, SLOT(disconnection()) );
connect(other, SIGNAL( destroyed(), this, SLOT(disconnection()) );

//...

void someClass::disconnection()
{
//someBaseClass *o = dynamic_cast<someBaseClass *>(sender()); //not needed
//disconnect(that, SIGNAL(/*xyz*/), other, SLOT(/*tuv*/)); //not needed

this->deleteLater(); //the whole point
}



If your slot does really only call deleteLater() you might find it interesting that deleteLater() is already a slot itself :)

Cheers,
_

themolecule
23rd May 2014, 22:17
Thanks! You mention an excellent point:



connect(that, SIGNAL(_something()), other, SLOT(_else()));
connect(that, SIGNAL( destroyed(), this, SLOT(deleteLater()) );
connect(other, SIGNAL( destroyed(), this, SLOT(deleteLater()) );


is more elegant.

d_stranz
24th May 2014, 17:15
Another thing the fine manual tells us is that not only is connect a static QObject method, it is also present as a member function. So, calling the static version:


connect(that, SIGNAL( destroyed(), this, SLOT(deleteLater()) );

is the same as calling the member version with this syntax:


connect(that, SIGNAL( destroyed(), SLOT(deleteLater()) );

I'm not really sure what you're trying to accomplish with all the this, that, and theOther code and deleteLater(). If you give your QObject instances non-NULL parents, then their lifetimes are controlled by those parents. As the usually fine but sometimes not complete enough manual says, all connections and events associated with those objects get cleaned up when the objects are destroyed, so except in rare circumstances it usually is unnecessary to call deleteLater() on something. If "this" is owned by "that", then when "that" is destroyed, so will be "this". If you really want the lifetime of "this" to be controlled by "that" (in essence, that's exactly what your signal / slot connection is doing), then make "this" a child of "that" and it will be taken care of for you.

The only times I have had to use deleteLater() is when I store a pointer to some instance in a data structure and I replace that pointer with a different instance in a slot. I'll call it then with the old pointer so things get cleaned up in a timely way. Otherwise, the memory used by the old pointer would hang around until the parent data structure goes away, along with the new one. You can't simply delete() the old pointer while in a slot without causing a crash. deleteLater() ensures the instance remains in scope until it is no longer needed.

themolecule
24th May 2014, 18:16
Thanks, that is also helpful.

What I'm trying to accomplish is essentially founded in Graph Theory.

In Qt, a connection 'just is'. It's made, and its lifetime is connected to the endpoints, but that's it.

However, if you consider the idea that a connection itself can be an object, like the relationship between a sender and receiver has a type and intention, and a sender and receiver can be connected many times with different roles, it becomes interesting. Within Qt, it may be that a signal and slot are being connected repeatedly, but the desire is that each of those connections are meaningful and unique. One could create lots of different signals and lots of slots to do this, but another way is to embed the intention as part of the signal itself, which would be a case for having a basic thing like:




class sender : public QObject
{
Q_OBJECT

public:
sender();

signals:
trigger(roleType role, QVariantMap data);
};

class receiver : public QObject
{
Q_OBJECT

public:
recevier();

slots:
triggered(roleType role, QVariantMap data);
};

...

//somewhere else
connect(_sender, SIGNAL(trigger(roleType,QVariantMap)), _receiver, SLOT(triggered(roleType,QVariantMap)));



But the sender may not know what its role is when emit()ing, and the connection wants to be smarter than that, so there wants to be an object layer and a connection layer that both offer intelligence to the system.

My thinking was that if connections are attached to the creator, then the lifetime of the connection object would take care of any cleanup. But instead it seems that the connection object wants to broker messages between endpoints and inject intelligence (filtering, queueing, routing, etc) that way. Which conceptually is simple enough but in Qt it ends up being a connect() from the sender to the connection, and then the connection to the receiver. It becomes a proxy link.

So now that I know that a connection object must maintain and proxy the connections (ie, Qt Signals/Slots aren't quite right all alone between objects for this purpose), I have to make connection objects that share qt signal/slot signatures (for API convenience), but internally the intelligence is in the connection object's polymorphism.

It becomes difficult when you have varieties of nodes that are descendants of a common base class who want to be agnostic to signals/slots, and the total set of connection roles are not known, but want to be able to be connected arbitrarily.

I'm trying to make something like Qt's signals/slots but with an extra bit in there that is not merely a connection, but a filtering/queueing/routing mechanism between threads and networked machines. It's a different layer of linkage, so parent/child associations are helpful during destructors, but a connection's lifetime may not be directly connected to the endpoints' lifetime.

So maybe this is better:




class base : public QObject
{
Q_OBJECT

public:
base(QObject *parent=0) : QObject(parent) {}
virtual ~base() {}
};

class sender : public base
{
Q_OBJECT

public:
sender(QObject *parent=0) : base(parent) {}
virtual ~sender() {}

signals:
trigger(QVariantMap data);
};

class receiver : public base
{
Q_OBJECT

public:
recevier(QObject *parent=0) : base(parent) {}
virtual ~receiver() {}

slots:
triggered(QVariantMap data);
};

class connection : public QObject
{
Q_OBJECT

public:
connection(base *sender, base *receiver) : QObject(sender)
{
connect(sender, SIGNAL(trigger(QVariantMap)), this, SLOT(incoming(QVariantMap)));
connect(this, SIGNAL(outgoing(QVariantMap)), receiver, SLOT(triggered(QVariantMap)));

connect(sender, SIGNAL(destroyed()), this, SLOT(deleteLater()));
connect(receiver, SIGNAL(destroyed()), this, SLOT(deleteLater()));
}
virtual ~connection()
{
//connections will be destroyed automatically here
}

signals:
void outgoing(QVariantMap data);

slots:
virtual void incoming(QVariantMap data)
{
//something interesting here which may or may not emit a signal, or be overridden by a subclass

if (interesting)
emit outgoing(data);
}
};

//other code...

//somewhere else
sender *_sender = new sender(this);
receiver *_receiver = new receiver(this);
connection *c = new connection(_sender, _receiver);



Sorry to ramble, but upon asking the question the structure becomes more clear, and those that have answered point out simple reductions that Qt's awesome engineering provide. It's surprisingly easy to overlook simple conveniences when swimming through complication!

thanks again!

d_stranz
24th May 2014, 18:31
I don't know where Qt actually stores connection instances, probably somewhere deep inside the meta-object system. So, because there *is* an instance of QMetaObject::Connection created when you call the connect() methods, there is a graph theoretical object associated. And you can attach properties to the connection (see QMetaObject::userProperty()). Undoubtedly somewhere in the connect() code, there are further connections made between destroyed() and deleteLater() on the two object instances being connected, otherwise the connection would have no way to ensure that the two instances were still valid.


but a connection's lifetime may not be directly connected to the endpoints' lifetime

Think about what you're saying here - that you can have an instance of a connection object that doesn't actually connect anything. If that's the case, then what purpose does it serve? Unless it is expensive to create the instance itself (not make the actually link between the two endpoints), then it might make sense to have a connection instance with a lifetime different from either endpoint. But usually the cost is in making the link, so in that case, why not have the connection exist only as long as both of its endpoints?

themolecule
24th May 2014, 19:11
I agree, and I just added a piece of code to clarify, that yes a Qt connection's lifetime is absolutely connected to its endpoints, and what my sample code shows is that what I'm calling a connection object can act the same way if properly cabled together.

I'm just making sure that I'm not missing something in the nuances of what Qt already has to offer.

Added after 26 minutes:

This is interesting...

QMetaObject::userProperty()

thanks!