1

I'm using Qt 5.9.2 with Visual Studio 2015 and QtDesigner for programming a Windows GUI application. I tried connecting one of my actions via the following call:

connect(ui.myAction, &QAction::triggered, memberPtrToObjX_, &ClassX::Run);

However ClassX::Run is not always triggered after clicking on myAction in the menubar. Investigating into this problem, I figured, that the same signal-slot connection using lambda syntax works:

connect(ui.myAction, &QAction::triggered, [this](bool run) { memberPtrToObjX_->Run(run); });

I'm pretty sure, that both calls are syntactically correct. Besides both calls return a valid QMetaObject::Connection, if I save the return value and check with operator bool().

Obviously I could just stick with the working lambda-version, but I'm confused and would prefer knowing the reason behind my "solution". Is there any functional difference between these two calls, that explains the different behaviour?

Don-Umbro
  • 165
  • 9
  • 1
    The only difference, I see: In the 1st version, the current pointer in `memberPtrToObjX_` (at the time of `connect()`) is used (always). The 2nd version uses the current pointer in `memberPtrToObjX_` at the time when the signal is triggered. If `memberPtrToObjX_` is modified after `connect()` - this makes a difference. – Scheff's Cat Feb 22 '18 at 17:04
  • The second form will always result in a connection of type [`Qt::DirectConnection`](http://doc.qt.io/qt-5/qt.html#ConnectionType-enum) rather than `Qt::AutoConnection` meaning that the emitter and receiver must always be on the same thread (generally speaking). – G.M. Feb 22 '18 at 17:18
  • @Scheff Thanks! That's a great point and theoretically answers my question. Unfortunately its not the solution here. In fact I'm pretty sure, the pointer isn't changed. I veryfied by changing my lambda call to be `connect(ui.myAction, &QAction::triggered, [memberPtrToObjX_](bool run) { memberPtrToObjX_->Run(run); });`. Now the `memberPtrToObjX_` should be captured by copy, similar to the first version. But still ClassX::Run is always triggered. – Don-Umbro Feb 23 '18 at 08:53
  • Could you provide a [mcve]? The 1st and 2nd version should really make no difference if `memberPtrToObjX_` is _not_ modified **and** its pointee [lives in](http://doc.qt.io/qt-5/qobject.html#thread-affinity) the same thread. (In this case, the first `connect()` will resolve [`Qt::AutoConnection`](http://doc.qt.io/qt-5/qt.html#ConnectionType-enum) to `Qt::DirectConnection`. This is actually what @G.M. already hinted.) Otherwise, (pointee "lives in" another thread) then... I believe, in this case, the 2nd version is a "time bomb" and only seems to work better. – Scheff's Cat Feb 23 '18 at 09:03
  • Out of curiosity: Are you sure that the `checked` arg. (in `QAction::triggered(bool checked)`) doesn't have any unintended effect? (I only ask because I don't know how your `QAction` is configured and what you do with `checked` in `ClassX::Run(bool)`.) Though, it still wouldn't explain the different behavior of `connect()`s... – Scheff's Cat Feb 23 '18 at 09:11
  • @G.M. Thanks for pointing that out. Using `connect(ui.myAction, &QAction::triggered, memberPtrToObjX_, &ClassX::Run, Qt::DirectConnection);` works. As Scheff pointed out, this might be a time bomb, so right now I'm looking further into the code... – Don-Umbro Feb 23 '18 at 09:31
  • @Scheff Thanks for all your efforts. Actually I tried creating a minimal example, but I couldn't construct one reproducing the bad behaviour ;). – Don-Umbro Feb 23 '18 at 09:48
  • This reminds me to [Eric Lippert](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). In case of unintended behavior which is difficult to explain, isolate things and examine them separately to find the _actual_ reason... – Scheff's Cat Feb 23 '18 at 09:54
  • **some story**: I figured the core of the problem. Under some circumstances ClassX is constructed inside a `std::async`. In fact ClassX is designed to be called from different (including non qt) threads, qt gui main thread being one of multiple owners of a shared_lock. **The question**: Is a Qt::DirectConnection always a bad idea for multi thread, even if the pointee internally protects all accesses via mutex / lock? [This](https://stackoverflow.com/a/15375994/9202566) just mentions it's bad, if callee is not thread-safe. **Metaquestion** Or should I better open a new topic for this? – Don-Umbro Feb 23 '18 at 10:33
  • @Scheff Regarding your link to Eric Lippert. I'll look into this. However isolating things led to my initial question recarding Qt::connect functionality. Maybe I started some hijacking of this question, when I tried to rebase your answer to my actual problem... – Don-Umbro Feb 23 '18 at 10:44
  • DirectConnection means the signal handler is called directly from the signal emitter. (In the past and X11, this was simply called "Callback".) Therefore, this doesn't work well in multi-threading except you consider this properly (which probably results in code terrible to read). – Scheff's Cat Feb 23 '18 at 10:47
  • With proper locking in the lambda (or in ClassX::Run() resp.) it should work. (IMHO) Multi-threading is always very explosive stuff in general - hard to understand due to the non-determinism. To make things worse - no compiler support - any error or bad design compiles unnoticed. Therefore, I handle this always with care (and less fantasy than other things). – Scheff's Cat Feb 23 '18 at 11:08
  • I'm not very experienced with [`std::async()`](http://en.cppreference.com/w/cpp/thread/async). For me, it sounds like - once you retrieved the result with `std::future::get()`, the "other" thread is joined. Is `ClassX` derived from `QObject`? If I remember right, a `QObject` stores it's thread were it was created in. This might be wrong after `std::future::get()`. May be, this is the problem... – Scheff's Cat Feb 23 '18 at 11:17
  • Please, have a look at this: [`QThread *QObject::thread() const`](http://doc.qt.io/qt-5/qobject.html#thread). In addition to the above, you could check whether your `ClassX` instance pretends "to live" in the "wrong" thread. – Scheff's Cat Feb 23 '18 at 11:21

1 Answers1

0

The two calls of QObject::connect() (exposed by the OP) behave differently in the case that this->memberPtrToObjX_ is modified after the call of connect().

The first

connect(ui.myAction, &QAction::triggered, memberPtrToObjX_, &ClassX::Run);

calls
QMetaObject::Connection QObject::connect(
const QObject *sender, PointerToMemberFunction signal,
const QObject *receiver, PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection)
.

Creates a connection of the given type from the signal in the sender object to the method in the receiver object. Returns a handle to the connection that can be used to disconnect it later.

Hence, the current pointer in this->memberPtrToObjX_ is connected as signal receiver. If this->memberPtrToObjX_ is modified after connect() this doesn't have any effect to the signal connection.

The second

connect(ui.myAction, &QAction::triggered, [this](bool run) { memberPtrToObjX_->Run(run); });

calls
QMetaObject::Connection QObject::connect(
const QObject *sender, PointerToMemberFunction signal,
Functor functor)
.

Creates a connection from signal in sender object to functor, and returns a handle to the connection.

Hence, the (functor behind the) lambda is connected as receiver. The lambda resolves the pointer in this->memberPtrToObjX_ at the time it is executed i.e. when the signal is triggered.


The second difference (which originally was uncovered in the comment of G.M.) is the connection type:

The first version uses the default value Qt::AutoConnection (as it is not defined explicitly). The version with the lambda uses always Qt::DirectConnection instead.

The difference appears if the pointee in this->memberPtrToObjX_ does not "live" in the same thread. In this case, the Qt::AutoConnection is resolved to Qt::QueuedConnection instead of Qt::DirectConnection.

I assumed that the pointee in this->memberPtrToObjX_ would "live" in the same QThread. If not, the second version (with the lambda) becomes very questionable as it calls the a member function of the object "living" in a different thread (where it is hard to tell what that thread is doing at this time). This only seems to work better but is very possibly a "time bomb".

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • Do you want to add some information on the difference regarding `Qt::AutoConnection` vs `Qt::DirectConnection`. Then I would happily accept your answer, because it clearly answers the posted question and might be useful in other contexts. Solving my actual problem is an independent task for myself. – Don-Umbro Feb 23 '18 at 10:37