【问题标题】:Qt Slots and C++11 lambdaQt 插槽和 C++11 lambda
【发布时间】:2013-11-12 05:15:33
【问题描述】:

我有一个 QAction 项目,我初始化如下:

QAction* action = foo->addAction(tr("Some Action"));
connect(action, SIGNAL(triggered()), this, SLOT(onSomeAction()));

然后 onSomeAction 看起来像:

void MyClass::onSomeAction()
{
    QAction* caller = qobject_cast<QAction*>(sender());
    Q_ASSERT(caller != nullptr);

    // do some stuff with caller
}

这很好,我得到了caller 对象,我可以按预期使用它。然后我尝试使用 C++11 的方式来连接对象,如下所示:

connect(action, &QAction::triggered, [this]()
{
    QAction* caller = qobject_cast<QAction*>(sender());
    Q_ASSERT(caller != nullptr);

    // do some stuff with caller
});

caller 始终为空,因此Q_ASSERT 会触发。如何使用 lambdas 获取发件人?

【问题讨论】:

  • @FrankOsterfeld:不完全是,因为在这里你不需要像假设的副本那样的任何体操。在这里,捕获作为参数提供给 connect 的显式可用发件人是一件小事。

标签: c++ qt c++11


【解决方案1】:

没有“this”上下文,例如来自 main():

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QLabel lbl{"Hello World!"};
    QPushButton btn;
    btn.show();
    lbl.show();

    QObject::connect(&btn, &QPushButton::clicked, [&lbl](){lbl.setText("Button clicked");});

    return a.exec();
}

【讨论】:

    【解决方案2】:

    使用 lambdas 作为插槽很简单(例如,对于来自 QSpinbox 的事件):

    connect(spinboxObject, &QSpinBox::editingFinished, this, [this]() {<do something>});
    

    但这只有在信号没有重载时才有效(这意味着有几个信号具有相同的名称但不同的参数)。

    connect(spinboxObject, &QSpinBox::valueChange, this, [this]() {<do something>});
    

    给出编译错误,因为存在两个重载信号: valueChanged(int) 和 valueChanged(const QString&) 所以有必要限定应该使用哪个版本:

    connect(spinboxObject, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int newValue){ });
    

    使用QOverload 更短一点(或更好的可读性):

    connect(spinboxObject, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int newValue) { });
    

    【讨论】:

      【解决方案3】:

      简单的答案是:你不能。或者,您不想(或不需要!)使用sender()。只需捕获并使用action

      //                                Important!
      //                                   vvvv
      connect(action, &QAction::triggered, this, [action, this]() {
          // use action as you wish
          ...
      });
      

      this 作为函子的对象上下文的规范确保了如果动作或thisQObject)不再存在,函子将不会被调用。否则,仿函数会尝试引用悬空指针。

      一般来说,在为传递给connect 的仿函数捕获上下文变量时,必须满足以下条件,以避免使用悬空指针/引用:

      1. connect 的源对象和目标对象的指针可以按值捕获,如上。保证如果调用函子,连接的两端都存在。

        connect(a, &A::foo, b, [a, b]{});
        

        ab 位于不同线程的场景需要特别注意。不能保证一旦进入functor,某个线程不会删除任何一个对象。

        一个对象仅在其thread() 中或thread() == nullptr 的任何线程中被破坏是惯用的。由于线程的事件循环调用函子,所以空线程对b 来说绝不是问题——没有线程,函子将不会被调用。唉,在b 的线程中,不能保证a 的生命周期。因此,通过值捕获动作的必要状态会更安全,这样a 的生命周期就不再是问题了。

        // SAFE
        auto aName = a->objectName();       
        connect(a, &A::foo, b, [aName, b]{ qDebug() << aName; });
        // UNSAFE
        connect(a, &A::foo, b, [a,b]{ qDebug() << a->objectName(); });
        
      2. 如果您绝对确定它们指向的对象的生命周期与连接的生命周期重叠,则可以按值捕获指向其他对象的原始指针。

        static C c;
        auto p = &c;
        connect(..., [p]{});
        
      3. 对对象的引用同上:

        static D d;
        connect(..., [&d]{});
        
      4. 不是从QObject 派生的不可复制对象应通过其共享指针按值捕获。

        std::shared_ptr<E> e { new E };
        QSharedPointer<F> f { new F; }
        connect(..., [e,f]{});
        
      5. QObjects 生活在同一个线程可以被QPointer 捕获;在函子中使用之前必须检查其值。

        QPointer<QObject> g { this->parent(); }
        connect(..., [g]{ if (g) ... });
        
      6. QObjects 生活在其他线程必须被共享指针或弱指针捕获。他们的父母必须在销毁之前取消设置,否则您将有双重删除:

        class I : public QObject {
          ...
          ~I() { setParent(nullptr); }
        };
        
        std::shared_ptr<I> i { new I };
        connect(..., [i]{ ... });
        
        std::weak_ptr<I> j { i };
        connect(..., [j]{ 
          auto jp = j.lock();
          if (jp) { ... }
        });
        

      【讨论】:

      • 这很漂亮,也很简单!答案正盯着我看。谢谢!
      猜你喜欢
      • 2013-03-15
      • 2016-10-22
      • 1970-01-01
      • 2011-04-08
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多