Qt Slots和C++ 11 lambda

Add*_*ddy 48 c++ qt c++11

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

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

然后onSomeAction看起来像:

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

    // do some stuff with caller
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,我得到了caller对象,我可以按预期使用它.然后我尝试使用C++ 11方式连接对象,如下所示:

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

    // do some stuff with caller
});
Run Code Online (Sandbox Code Playgroud)

但是caller始终为null,因此Q_ASSERT触发器.我怎样才能使用lambdas来获取发件人?

Rei*_*ica 74

简单的答案是:你做不到.或者,您不希望(或需要!)使用sender().只需捕获和使用action.

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

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

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

  1. 如上所述,connect可以通过值捕获指向源和目标对象的指针.保证如果调用仿函数,则存在连接的两端.

    connect(a, &A::foo, b, [a, b]{});
    
    Run Code Online (Sandbox Code Playgroud)

    不同线程中的场景a和场景b需要特别注意.无法保证一旦输入仿函数,某些线程就不会删除任何一个对象.

    如果对象仅在其中thread()或在任何线程中被破坏,这是惯用的thread() == nullptr.由于线程的事件循环调用了仿函数,因此空线程永远不会成为问题b- 如果没有线程,则不会调用仿函数.唉,不能保证ain bthread 的生命周期.因此,通过价值来捕捉行动的必要状态是更安全的,因此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(); });
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果您完全确定它们指向的对象的生命周期与连接的生命周期重叠,则可以通过值捕获到其他对象的原始指针.

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

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

    std::shared_ptr<E> e { new E };
    QSharedPointer<F> f { new F; }
    connect(..., [e,f]{});
    
    Run Code Online (Sandbox Code Playgroud)
  5. QObject生活在同一个线程中的人可以被一个人捕获QPointer; 在使用仿函数之前必须检查其值.

    QPointer<QObject> g { this->parent(); }
    connect(..., [g]{ if (g) ... });
    
    Run Code Online (Sandbox Code Playgroud)
  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) { ... }
    });
    
    Run Code Online (Sandbox Code Playgroud)


MiB*_*der 12

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

connect(spinboxObject, &QSpinBox::editingFinished, this, [this]() {<do something>});
Run Code Online (Sandbox Code Playgroud)

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

connect(spinboxObject, &QSpinBox::valueChange, this, [this]() {<do something>});
Run Code Online (Sandbox Code Playgroud)

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

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

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

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