35

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
  • 12,102
  • 18
  • 74
  • 171
  • 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

49

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.release();
        // ...
 });
Carsten
  • 10,430
  • 7
  • 37
  • 58
ecatmur
  • 137,771
  • 23
  • 263
  • 343
  • 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
  • 5
    This code seems not tested .. and likely not working. – Mohammad Kanan Jan 05 '20 at 00:31
2

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

QObject *context = new QObject(this);
connect(sender, &Sender::signal, context, [context] {
  delete context;
  // ...
});
Jason Haslam
  • 2,183
  • 9
  • 16
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
  • 319
  • 3
  • 5