16

Is there a way to disconnect Qt connections that are made to lambda functions without storing connection objects?

I know it's possible to do if I store the QMetaObject::Connection returned from the connect function, but I don't really want to do that because there would be a ton of them. I mainly connect to lambda functions to avoid creating a bunch of one-off methods and objects, and it seems like if I need to do all that bookkeeping that SLOTs would be more preferable.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Nicolas Holthaus
  • 7,763
  • 4
  • 42
  • 97

4 Answers4

25

Assuming a connection:

QObject::connect(senderInstance, &Sender::mySignal, this, []() {
    // implement slot as a lambda
});

Then you can easily disconnect by:

QObject::disconnect(senderInstance, &Sender::mySignal, this, nullptr);

This would disconnect all of this'sslots for Sender::mySignal; however, it is quite common to have only one such slot, so the end result is the disconnection is performed simply and with no side effect.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
David Ching
  • 1,903
  • 17
  • 21
10

You can use a dummy object:

QObject *obj = new QObject(this);
QObject::connect(m_sock, &QLocalSocket::readyRead, obj, [this](){
   obj->deleteLater();

When the obj is destroyed, the connection is disconnected because you passed the obj on the connect.

mabg
  • 1,894
  • 21
  • 28
7

Here are two approaches to hide the bookkeeping issues.

First, we maintain a std::vector which, on destruction, disconnects us from the source:

typedef std::shared_ptr<void> listen_token;

struct disconnecter {
  QMetaObject::Connection conn;
  disconnecter(   QMetaObject::Connection&& c ):conn(std::move(c)) {}
  ~disconnecter() { QObject::disconnect(conn); }
};

template<class F, class T, class M>
listen_token QtConnect( T* source, M* method, F&& f ) {
  return std::make_shared<disconnecter>(
    QObject::connect( source, method, std::forward<F>(f));
  );
}

typedef std::vector<listen_token> connections;

Then we connect as follows:

connections conns;
conns.emplace_back( QtConnect( bob, &Bob::mySignal, [](QString str){ std::cout << "Hello World!\n"; } ) );

when the vector is destroyed, the connection objects are also destroyed.

This is similar to how other signal/slot systems are handled, where the listener keeps track of a token, then returns it. But here, I keep the disconnection object in an opaque type that cleans up the connection on destruction.

Note that copying that vector will extend the lifetime of the connection. If the message is going to a particular instance of a class, store a connections instance in the class, and you won't get messages after the instance is destroyed.


A second approach, based off what @lpapp found, is if you have a lambda that you want to call only once in response to a signal, then disconnect:

template<class F>
struct auto_disconnect_t {
  F f;
  std::shared_ptr<QMetaObject::Connection> conn;

  template<class U>
  auto_disconnect_t(U&& u):
    f(std::forward<U>(u)),
    conn(std::make_shared<QMetaObject::Connection>())
  {}

  template<class... Args>
  void operator()(Args&&... args)const{
    QObject::disconnect(*conn);
    f( std::forward<Args>(args)... );
  }
};

template<class T, class M, class F>
void one_shot_connect( T* t, M* m, F&& f ) {
  typedef typename std::decay<F>::type X;
  auto_disconnect_t<X> helper(std::forward<F>(f));
  *helper.conn = QObject::connect( t, m, helper );
};

here we one_shot_connect( bob, &Bob::mySignal, [](QString str) { std::cout << "Hello\n" } );, and the next time the signal fires we get the message, and then the connection is disconnected.

I disconnect before processing your lambda, just in case the lambda causes the signal to fire or something.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    Why are you using shared_ptr instead of unique_ptr here? – Luke Worth Jan 12 '15 at 05:41
  • 2
    @povman `std::function` and most type erasures of the concept *invoke* require *copy* as well (in the second case above), and `unique` would block *copy*. In the first, `unique` would work, but require more typing (to store an explicit destroyer), or less abstraction (to store an explicit target in the token, instead of `void`). And other listener-observer systems require different data in the listener token, so I am in the habit of using `shared_ptr` for it. – Yakk - Adam Nevraumont Jan 12 '15 at 11:21
  • Great idea, however error with template argument deduction and I have no idea why. Does the code in the first approach compile (using bob or some other example)? I get an error C2784: 'listen_token QtConnect(T *,M *,F &&)': could not deduce template argument for 'M *' from 'void (__cdecl QIODevice::* )(qint64)' when I try to use this with a QSerialPort. I call as follows QtConnect(mPort.get(), &QSerialPort::bytesWritten, [&](qint64 bytes) {...}; Any ideas on how I can fix this? the legacy connect works fine connect(mPort.get(), &QSerialPort::bytesWritten, this, &MainWindow::processTx); – johnco3 Mar 04 '17 at 05:21
  • I am unable to compile this, with g++ 7.3 and using c++17. Anyone had success? – eudoxos Mar 05 '19 at 10:12
  • @eudoxos Which one? I don't have an online Qt compiler nor an offline Qt-enabled one right now, but I read over the first block and I don't see anything obviously wrong. I mean I missed a `&` before a `Bob::mySignal` in the 2nd to last paragraph? And I make lots of assumptions above lack of overloads and signatures of the methods and the like. – Yakk - Adam Nevraumont Mar 05 '19 at 13:41
  • Sorry, it is about the templated `one_shot_connect` (I call it `QObject_connect_one_shot`, otherwise same code), the invocation being `QObject_connect_one_shot(psdSplitter,&PsdSplitter::tareDone,[this](qreal zDist, qreal zSkew){ /* lambda body */ });`, getting the error: `no matching function for call to ‘QObject_connect_one_shot(PsdSplitter*&, void (PsdSplitter::*)(float, float), PsdUiData::tareLaunch(qreal)::)’`. – eudoxos Mar 05 '19 at 14:49
  • The rest of the compiler error: `note: candidate: template void QObject_connect_one_shot(T*, M*, F&&) void QObject_connect_one_shot( T* t, M* m, F&& f ) {` / `note: template argument deduction/substitution failed:` / `note: mismatched types ‘M*’ and ‘void (PsdSplitter::*)(float, float)’`. – eudoxos Mar 05 '19 at 14:50
1

This is the only way as of writing this:

QMetaObject::Connection connection =
    QObject::connect(psender, &MyClass::mySignal, [] () { /* Do the work */ });
QObject::disconnect(connection);

See the documentation for reference about the disconnect method.

bool QObject::disconnect(const QMetaObject::Connection & connection) [static]

Disconnect a connection.

If the connection is invalid or has already been disconnected, do nothing and return false.

In the end of the day, you will still do the book keeping with slots, too, only that lambda seems to be more localized if you really need it localized, so it is up to your personal preference, I assume.

Community
  • 1
  • 1
László Papp
  • 51,870
  • 39
  • 111
  • 135