57

我有一个 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 获取发件人?

4

3 回答 3

94

简单的答案是:你不能。或者,更确切地说,您不想(或不需要!)使用sender(). 只需捕获和使用action.

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

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

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

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

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

    a不同线程中的场景b需要特别注意。不能保证一旦进入函子,某些线程不会删除任何一个对象。

    一个对象仅在其thread()或任何线程中被破坏是惯用的thread() == nullptr。由于线程的事件循环调用函子,所以空线程永远不会成为问题b- 没有线程,函子将不会被调用。唉,不能保证ainb线程的生命周期。因此,通过值来捕获动作的必要状态会更安全,因此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. QObject生活在同一个线程中的 s 可以被 a 捕获QPointer;在函子中使用之前必须检查它的值。

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

    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-11-01T05:08:46.707 回答
15

使用 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) { });
于 2017-01-22T13:20:49.550 回答
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();
}
于 2019-09-24T11:49:49.470 回答