【问题标题】:How to disconnect a lambda function without storing connection [duplicate]如何在不存储连接的情况下断开 lambda 函数 [重复]
【发布时间】:2014-12-20 14:14:16
【问题描述】:

有没有办法在不存储连接对象的情况下断开与 lambda 函数建立的Qt 连接?

我知道如果我存储从 connect 函数返回的 QMetaObject::Connection 是可能的,但我真的不想这样做,因为它们会有很多。我主要连接到 lambda 函数以避免创建一堆一次性的方法和对象,如果我需要做所有的簿记,SLOTs 似乎更可取。

【问题讨论】:

  • 如果不再次存储某种状态,你会如何想象你会这样做?
  • 我无法想象如何,这就是我问社区的原因。似乎如果没有办法,lambda语法对于需要断开连接的情况并不是很有用。

标签: c++ qt c++11 lambda


【解决方案1】:

这是写这篇文章的唯一方法:

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

请参阅documentation 以获取有关断开连接方法的参考。

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

断开连接。

如果连接无效或者已经断开,什么都不做,返回false。

归根结底,您仍然会使用插槽记账,只是如果您确实需要本地化 lambda,它似乎更加本地化,​​所以这取决于您的个人喜好,我想。

【讨论】:

    【解决方案2】:

    这里有两种隐藏簿记问题的方法。

    首先,我们维护一个std::vector,它在销毁时会断开我们与源的连接:

    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;
    

    然后我们连接如下:

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

    当向量被销毁时,连接对象也被销毁。

    这类似于其他信号/槽系统的处理方式,侦听器跟踪令牌,然后返回它。但在这里,我将断开连接对象保留在不透明类型中,以便在销毁时清除连接。

    请注意,复制该向量将延长连接的生命周期。如果消息要发送到某个类的特定实例,请在该类中存储一个connections 实例,实例销毁后您将不会收到消息。


    基于@lpapp 发现的第二种方法是,如果您有一个只想调用一次以响应信号的 lambda,然后断开连接:

    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 );
    };
    

    这里我们one_shot_connect( bob, &amp;Bob::mySignal, [](QString str) { std::cout &lt;&lt; "Hello\n" } );,下次信号触发时我们会收到消息,然后连接断开。

    我在处理您的 lambda 之前断开连接,以防 lambda 导致信号触发或发生其他情况。

    【讨论】:

    • 为什么在这里使用 shared_ptr 而不是 unique_ptr?
    • @povman std::function 和大多数类型擦除概念 invoke 也需要 copy (在上面的第二种情况下)和@987654328 @ 会阻止复制。首先,unique 可以工作,但需要更多类型(存储显式销毁器)或更少抽象(在令牌中存储显式目标,而不是 void)。和其他监听器-观察者系统需要不同的监听器令牌数据,所以我习惯使用shared_ptr&lt;void&gt;
    • 好主意,但是模板参数推导出错,我不知道为什么。第一种方法中的代码是否编译(使用 bob 或其他示例)?我收到错误 C2784: 'listen_token QtConnect(T ,M *,F &&)': could not deduc template argument for 'M *' from 'void (__cdecl QIODevice:: )(qint64)'当我尝试将它与 QSerialPort 一起使用时。我调用如下 QtConnect(mPort.get(), &QSerialPort::bytesWritten, [&](qint64 bytes) {...}; 关于如何解决这个问题的任何想法?旧版连接工作正常 connect(mPort.get( ), &QSerialPort::bytesWritten, this, &MainWindow::processTx);
    • 我无法使用 g++ 7.3 和 c++17 编译它。有人成功了吗?
    • @eudoxos 哪一个?我现在没有在线 Qt 编译器,也没有启用离线 Qt 的编译器,但我阅读了第一个块,没有发现任何明显错误。我的意思是我在倒数第二段的Bob::mySignal 之前错过了&amp;?而且我在缺乏方法的重载和签名等方面做了很多假设。
    【解决方案3】:

    假设连接:

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

    然后您可以通过以下方式轻松断开连接:

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

    这将断开Sender::mySignal 的所有this'sslots;然而,只有一个这样的插槽是很常见的,所以最终的结果是断开连接很简单,没有副作用。

    【讨论】:

    • 谢谢大卫!我同意这可能涵盖 60-70% 的场景。
    • 效果很好,谢谢
    • myObject-&gt;disconnect(SIGNAL(mySignal()));
    • @NicolasHolthaus 与QObject 相关的所有内容都在~QObject() 中断开连接,这本身就涵盖了很多内容
    • @caleth 当然,这仅适用于您不想破坏连接对象的情况
    【解决方案4】:

    您可以使用虚拟对象:

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

    当obj被销毁时,连接被断开,因为你在connect上传递了obj。

    【讨论】:

    • 我喜欢这个解决方案,因为它更简单、更干净。只需捕获 obj 即可进行调用。
    • 我在运行时收到QObject: Cannot create children for a parent that is in a different thread.
    • ... 或使用this 作为虚拟对象,如果它是QObject
    猜你喜欢
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    • 2017-11-18
    • 2021-06-24
    • 2023-02-08
    • 1970-01-01
    • 2015-11-25
    相关资源
    最近更新 更多