9

Is it possible, and if so, how can I create a signal/slot in Qt that is a const reference to a shared_ptr? I want a signal that looks like this:

void signal( shared_ptr<SomeClass> const & )

I know how to do this without a constant reference, that is simply the type shared_ptr<SomeClass> but for efficiency* reasons I'd like to avoid the copying. The same syntax for reference type isn't working:

Q_DECLARE_METATYPE(shared_ptr<SomeClass> const &)
qRegisterMetaType<shared_ptr<SomeClass> const&>();

Many of the standard APIs have QString const & so I assume it is fundamentally possible and I just can't figure out the syntax.


**The biggest problem for performance is not the copying time, but the amount of mutex locking/unlocking as the object is copied to every receiver -- there are lots of them. As multiple threads use the object this introduces a noticeable slow-down/bottleneck. If the shared_ptr does in fact just use an atomic op this cost is also trivial, the general question about const reference in signals remains however.*

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • Doesn't passing a shared_ptr as a const ref negate the point of having a shared_ptr in the first place? Just pass a const ref / pointer to the object in question. – messenger Jan 26 '11 at 18:21
  • 1
    @messenger, the const reference is a optimization to avoid copying the shared_ptr when calling functions. It is a fairly common pattern. If you always pass by value (signals or normal functions) shared_ptr's become quite costly in comparison to normal pointers. Normal pointers can not be used here as the lifetime of the object from the "emit" side is not guaranteed. – edA-qa mort-ora-y Jan 26 '11 at 18:35
  • @edA-qa mort-ora-y: Does the boost shared_ptr use a mutex??? Why that??? Aren't atomic increment/decrement all you need? – mmmmmmmm Jan 26 '11 at 18:59
  • @edA-qa, if the lifetime isn't guaranteed, then why did you try to use references instead of copying a pointer object? Wouldn't you just get a dangling reference if the object was destroyed on the other end? – Sergei Tachenov Jan 26 '11 at 19:10
  • @rstevens, I used the term "mutex" too loosely. Hopefully they are just using an atomic. Hmm, if that is really true then I might be okay since at worst it just cause a cache flush. – edA-qa mort-ora-y Jan 26 '11 at 19:11
  • @Sergey, exactly the reason why I'm using a shared_ptr. – edA-qa mort-ora-y Jan 26 '11 at 19:12
  • @edA-qa mort-ora-y I would only worry about doing the optimization if you actually have a *measured* performance issue. To my knowledge, passing a shared_ptr around as a const ref defeats the purpose of the shared_ptr (i.e. having a shared reference between multiple objects) If you can actually determine a performance issue, just pass around a pointer and manage its lifetime yourself. – messenger Jan 26 '11 at 19:28
  • @edA-qa mort-ora-y Just wanted to follow up to Sergey's point, which I don't think you are getting. Obvious thing here: If you make a ref to an object and the original object goes out of scope, the ref becomes invalid. Nonobvious thing: if you don't expect the ref to ever become invalid, then that implies that the original shared_ptr will never go out of scope, thus there is no need for it to be a shared_ptr in the first place? – messenger Jan 26 '11 at 19:34
  • @edA-qa, I agree with messenger here. If you pass a reference to a shared_ptr whose lifetime isn't guaranteed, would the shared_ptr do its job properly? I doubt it, since it won't increase the reference counter. You'll just get an invalid reference to the memory where the shared_ptr used to exist. – Sergei Tachenov Jan 26 '11 at 19:37
  • @edA-qa mort-ora-y I found this answer about passing around a shared_ptr as a reference http://stackoverflow.com/questions/327573/c-passing-references-to-boostshared-ptr – messenger Jan 26 '11 at 19:43
  • Well, I agree that the "emit" side should take one copy of the shared_ptr, I was just hoping the receiver side could connect by const reference (to avoid the copy). – edA-qa mort-ora-y Jan 26 '11 at 20:06
  • I do have performance tests of everything involved. Copying a shared_ptr is extremely slow compared to not-copying it. Relative to the overhead of the signalling system it isn't horrible. The shared_ptr is taking about 15ns to copy on my test machine. – edA-qa mort-ora-y Jan 26 '11 at 20:07
  • @edA-qa mort-ora-y Maybe this is one of those situations when you should abandon the shared_ptr and just manage the lifetime of a normal pointer yourself? – messenger Jan 26 '11 at 20:24
  • 1
    @edA-qa, oh, now I get it. You wanted to make just one copy that would get passed by reference to each connected slot. Now it's a good idea, but unfortunately Qt does things in a different way. I guess you could get around it by first passing a copy of your pointer to some proxy object living in the receiving thread, which would in turn signal all the receivers using direct connections, but I'm afraid that double signaling overhead would defeat the very point of avoiding unnecessary copying. – Sergei Tachenov Jan 27 '11 at 06:32

3 Answers3

8

So far I have found that I can simply do this:

Q_DECLARE_METATYPE(shared_ptr<SomeClass>)
qRegisterMetaType<shared_ptr<SomeClass> >();
qRegisterMetaType<shared_ptr<SomeClass> >("std::shared_ptr<SomeClass>const&");

I'm trying to verify now whether this truly works correctly. The documentation here isn't clear on what actually happens. It appears that the const reference type in a signal/slot will just be marshalled as a normal shared_ptr<SomeClass>, which is totally okay here. Having some sort of guarantee that this is supposed to work as such would be nice however.

I have the feeling that the simple shared_ptr<SomeClass> version is all that is needed and it is the boost namespace that is interfering with the signals. The second version appears just to register the signal in the global namespace for easier use.


I can confirm, from testing, that the const & part is, as alluded to, completely ignored in queued connections. Each connected slot gets a new copy of the object. This is very unfortunate. :(

Further tests show that the & is used for the slot, but in an unusual fashion. The copy of the object is still created for the queued connection, but if you don't use a reference another copy will be created for the call.

Thus there although every connect will end up copying the data for a queued connection, the references still help a bit. And also if you do have a few signals sent locally (same thread) you may avoid even more copying.

Lord Zsolt
  • 6,492
  • 9
  • 46
  • 76
edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 1
    1) You should be able to omit the string argument from qRegisterMetaType() alltogether. 2) const ref is never needed for signal/slots and the metatype stuff, just the plain type. Also const ref. and passing by value are equivalent to the signal/slot mechanism: You can connect a signal void signal( const T& ) via SIGNAL(signal(const T&)) or SIGNAL(signal(T)), the signature is normalized to "signal(T)" anyway. When marshalled e.g. for a queued connection, the value is stored by copying, not by storing the const ref. – Frank Osterfeld Jan 26 '11 at 18:47
  • @Frank, for the queuing I'm fine with a copy being taken. I'm concerned with the dispatching to receivers. If I connect many slots to a signal with a reference, will a reference to the same object be used in all cases (that is, all slots within the same thread)? – edA-qa mort-ora-y Jan 26 '11 at 18:50
  • 1
    qa, I just checked the sources and it looks like Qt creates a copy for each queued connection, that is, for each receiver. If you wish, check QMetaObject::activate() which loops through senders and QMetaObject::queued_activate() which does actual copying, all in the qobject.cpp. – Sergei Tachenov Jan 26 '11 at 19:40
  • You must add the `std::` namespace inside the string, since `using namespace` doesn't affect that. – Lord Zsolt Jan 06 '15 at 00:29
2

According to one of the answer in this question Argument type for Qt signal and slot, does const reference qualifiers matters? , for a queued connection, the object is copied regardless of how you connect the signal and slot.

Since you are using multiple threads, the connection is queued. If you fear the mutex cost, try using SomeClass * directly.

Community
  • 1
  • 1
Dat Chu
  • 10,822
  • 13
  • 58
  • 82
  • Copying across the threads is totally okay so long as it is just using the copy constructor (or operator=). Its within one thread I don't want copying to all the receivers. – edA-qa mort-ora-y Jan 26 '11 at 18:32
0

For the sake of completeness, I believe it should be mentioned that Qt has its own shared pointer implementation, QSharedPointer, which behaves exactly like std::shared_ptr. Eventually you will still need to register your argument type though.

J. Birkner
  • 56
  • 5