Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QObject* context in QObject::connect function

Tags:

c++

c++11

qt

I'v read the documentation for QObject::connect (for Qt 5.4), but I have a question about the overload

QMetaObject::Connection QObject::connect(const QObject * sender, PointerToMemberFunction signal, const QObject * context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)

What exactly is the context parameter? What is its purpose? Can it be used to build connections in local event loops in threads?

Can someone provide examples of how/when to use this overload (when the context is not this)?

like image 374
Nicolas Holthaus Avatar asked Jan 14 '15 21:01

Nicolas Holthaus


1 Answers

The context object is used in two scenarios.

Automatic disconnection

Let's first do a step back and ask ourselves: when does Qt break a connection?

With the usual connect(sender, signal, receiver, slot) connect, there are three possibilities:

  1. When someone explicitely calls disconnect;
  2. When sender is deleted;
  3. When receiver is deleted.

Especially in cases #2 and #3, it just makes sense for Qt to behave that way (actually, it must behave that way, otherwise you'd have resource leaks and/or crashes).


Now: when using the connect overload taking a functor, when does Qt break a connection?

Note that without the context parameter, there's only one QObject involved: the sender. Hence the answer is:

  1. When someone explicitely calls disconnect;
  2. When sender is deleted.

Of course, there's no receiver object here! So only the sender automatically controls the lifetime of a connection.

Now, the problem is that the functor may capture some extra state that can become invalid, in which case is desirable that the connection gets broken automatically. The typical case is with lambdas:

connect(sender, &Sender::signal, 
        [&object1, &object2](Param p) 
        { 
            use(object1, object2, p);
        }
);

What happens if object1 or object2 get deleted? The connection will still be alive, therefore emitting the signal will still invoke the lambda, which in turn will access destroyed objects. And that's kind of bad...


For this reason, when it comes to functors, a connect overload taking a context object has been introduced. A connection established using that overload will be disconnected automatically also

  1. when the context object is deleted.

You're probably right when you say that a good number of times you're going to see there the very same "main" object used in the functor, for instance

connect(button, 
        &QPushButton::clicked,
        otherWidget, 
        [otherWidget]() 
        { 
            otherWidget->doThis(); otherWidget->doThat(); 
        }
);

That's just a pattern in Qt -- when setting up connections for sub-objects, you typically connect them to slots on this object, hence this is probably the most common context. However, in general, you may also end up with something like

// manages the lifetime of the resources; they will never outlive this object
struct ResourceManager : QObject 
{
    Resource res1; // non-QObjects
    OtherResource res2;
};

ResourceManager manager;    
connect(sender, signal, manager, [&manager](){ use(manager.res1, ...); });
// or, directly capture the resources, not the handle

So, you're capturing part of the state of manager.


In the most general case, when no context object is available, if there's the chance that the objects captured by the lambda survive the connection, then you must capture them by weak pointers, and try to lock those pointers inside the lambda before trying to access them.

Running a functor in a specific thread/event loop

Very shortly: when specifying a context object, the functor will be run into the context's thread, just like normal connections employing a receiver object. Indeed, note that the connect overload that takes a context also takes a connection type (while the one without context doesn't take one -- connection is always direct).

Again, this is useful because QObject is not reentrant or thread safe, and you must use a QObject only in the thread it lives in. If your functor accesses an object living in another thread, it must be executed in that thread; specifying that object as the context solves the issue.

like image 95
peppe Avatar answered Oct 15 '22 04:10

peppe