45

Is it possible to disconnect a lambda function? And if "yes", how?

According to https://qt-project.org/wiki/New_Signal_Slot_Syntax I need to use a QMetaObject::Connection which is returned from the QObject::connect method, but then how can I pass that object to the lambda function?

Pseudo-code example:

QMetaObject::Connection conn = QObject::connect(m_sock, &QLocalSocket::readyRead, [this](){
    QObject::disconnect(conn); //<---- Won't work because conn isn't captured

    //do some stuff with sock, like sock->readAll();
}
alexandernst
  • 14,352
  • 22
  • 97
  • 197
  • Have you tried that? (But also add `conn` to the capture list for the lambda) – Some programmer dude Feb 12 '13 at 08:51
  • @JoachimPileborg Yes, it segfaults for some reason. As soon as I remove the QMetaObject::Connection conn and leave only the code after the = the segfault dissapears. – alexandernst Feb 12 '13 at 08:52
  • 2
    The problem is discussed here: http://stackoverflow.com/questions/13847507/qt5-new-signal-to-lambda-connections-memory-leak – kfunk Feb 12 '13 at 09:33
  • @kfunk I did see that question, but I can't get any conclusion of it. Can you explain a little bit further how and why could it be done? – alexandernst Feb 12 '13 at 09:37

3 Answers3

63

If you capture conn directly, you're capturing an uninitialised object by copy, which results in undefined behaviour. You need to capture a smart pointer:

std::unique_ptr<QMetaObject::Connection> pconn{new QMetaObject::Connection};
QMetaObject::Connection &conn = *pconn;
conn = QObject::connect(m_sock, &QLocalSocket::readyRead, [this, pconn, &conn](){
    QObject::disconnect(conn);
    // ...
}

Or using a shared pointer, with slightly greater overhead:

auto conn = std::make_shared<QMetaObject::Connection>();
*conn = QObject::connect(m_sock, &QLocalSocket::readyRead, [this, conn](){
    QObject::disconnect(*conn);
    // ...
}

From Qt 5.2 you could instead use a context object:

std::unique_ptr<QObject> context{new QObject};
QObject* pcontext = context.get();
QObject::connect(m_sock, &QLocalSocket::readyRead, pcontext,
    [this, context = std::move(context)]() mutable {
    context.reset();
        // ...
 });
O'Neil
  • 3,790
  • 4
  • 16
  • 30
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    Could you please explain the first example in more detail? Why do you create pointer, then reference to the pointer and pass both to the lambda? Edit: Isn't it possible to do something like: *pconn = QObject::connect(...); end leave the conn completely out of it? – stepanbujnak Jul 06 '13 at 15:59
  • 1
    In the first example, the lifetime of `conn` is determined by the code block, while the lambda function still uses it. Sheer luck if `conn` is still alive. In this scenario, you definitely need a `shared_ptr`. – xtofl Feb 06 '15 at 08:18
  • @xtofl `conn` in the first example is a reference; its lifetime is the lifetime of the `unique_ptr`, which is captured into the lambda. (This should be a C++14 init-capture, really.) – ecatmur Feb 06 '15 at 21:33
  • 2
    Note that since Qt 5.2 there is also an overload that takes a context object. The connection will then be disconnected when the sender or the context object is destroyed. – Zitrax Aug 10 '17 at 14:33
  • @PredragManojlovic, `context` is of type `unique_ptr<>`. By doing `context.clear()`, the `unique_ptr` is deleted. However, I am still not clear, why the simple `QObject` is referred as "context" object. It seems to be a mechanism and not some type. – iammilind Jul 22 '19 at 04:58
  • 1
    @iammilind `clear` is not a member of `std::unique_ptr`. `release` deletes the pointer. `context` is simply a synchronization object. By connecting the slot to the this instance and deleting it in the handler, Qt automatically also releases the signal-slot connection. Qt does this for all connections, if the receiver is deleted. – Carsten Jul 24 '19 at 09:10
  • 6
    This code seems not tested .. and likely not working. – Mohammad Kanan Jan 05 '20 at 00:31
  • 2
    A little late to the party, but `release` of `unique_ptr` relinquishes ownership to the caller, meaning you are now responsible for deallocating the context object manually (potentially with `deleteLater`). What you were probably looking for is `context.reset()` – Refugnic Eternium Sep 23 '21 at 07:54
8

The context solution from ecatmur's answer is the easiest option, but I think the use of the smart pointer makes it hard to understand. I prefer to use a raw pointer instead:

QObject *context = new QObject(this);
connect(sender, &Sender::signal, context, [context] {
  delete context;
  // ...
});
Jason Haslam
  • 2,617
  • 13
  • 19
0

You can define the conn as a private variable in the .h file. QMetaObject::Connection conn.
in the lambda function, you can use conn and disconnect it.

 conn = QObject::connect(m_sock, &QLocalSocket::readyRead, [=](){
    QObject::disconnect(conn); //<---- Won't work because conn isn't captured

    //do some stuff with sock, like sock->readAll();
}
Kaveh Safavi
  • 499
  • 6
  • 5