使用C++11 lambda函数将点击事件连接到函数时出现问题

Trouble using C++11 lambda function to connect a click event to a function

本文关键字:函数 连接 问题 lambda C++11 使用 事件      更新时间:2023-10-16

在GUI应用程序中,我使用了不少按钮。这些按钮被标记为pbxx,其中xx是网格布局中按钮的行和列编号。按下按钮时,需要突出显示该按钮。今天我读到关于lambda函数的BrianPoteat&Kuba Ober,并认为我会尝试在我的代码中实现这一点。

在我的GuiDisplay类(一个继承QMainWindow的类)中,我有一个名为的函数

make_connections(); 

这将使我所有的按钮连接(所有信号都连接到一个单独的插槽on_pushbutton_clicked()。我在这里添加了代码:

GuiDisplay类

class GuiDisplay : public QMainWindow
{
    Q_OBJECT
public:
    explicit GuiDisplay(QWidget *parent = 0);
    ~GuiDisplay();
    ... Some more public functions ...
    /**
     * @brief Connects all custom signals and slots.
     */
    void make_connections();
    /**
     * @brief Will highlight the provided pushbutton with provided rgb colour
     */
    void highlight_pushbutton(QPushButton &pb, const int rgb[]);
private slots:
    void on_pushbutton_clicked(const QString& label);
private:
    Ui::GuiDisplay *ui;
};

GuiDisplay类的make_connections函数

void GuiDisplay::make_connections()
{
    // Loop through all the pushbuttons and connect clicked signal to clicked slot
    QString pb_label = "";
    for(int i = 0; i < 8; ++i)
    {
        for(int j = 0; j < 8; ++j)
        {
            // Find the pushbutton
            pb_label = "pb" + QString::number(i) + QString::number(j);
            QPushButton* pb_ptr = this->findChild<QPushButton*>(pb_label);
            //connect(pb_ptr, SIGNAL(clicked()), this, SLOT(on_pushbutton_clicked()));
            connect(pb_ptr, &QPushButton::clicked, [this]{on_pushbutton_clicked(pb_label);});
        }
    }
}

当我连接时,问题出现了

connect(pb_ptr, &QPushButton::clicked, [this]{on_pushbutton_clicked(pb_label);});

该版本给了我以下错误

'pb_label' is not captured

所以我想,好吧,那么做以下事情似乎不会错:

connect(pb_ptr, &QPushButton::clicked, [this, &pb_label]{on_pushbutton_clicked(pb_label);});

构建时的错误消失了,但是每当执行此代码时,我的GUI应用程序都会意外崩溃。不知道为什么。这里有人帮忙吗?

您的错误是由于使用了对临时的引用。pb_label仅在GuiDisplay::make_connections的持续时间内存在,您的lambda由Qt存储,并且每次触发信号时都会被引用。当这种情况发生时,您将引用一个已销毁的对象。

因此,正确的连接方式是:

connect(pb_ptr, &QPushButton::clicked, [=]{ on_pushbutton_clicked(pb_label); });

从Qt 5.2开始,添加了QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)过载,该过载将在对象生命周期结束时自动断开连接。在这种情况下,连接将是:

connect(pb_str, &QPushButton::clicked, this, [=]{ on_pushbutton_clicked(pb_label); });

也就是说,当你可以简单地使用时,你不应该在这里使用lambda

connect(pb_ptr, &QPushButton::clicked, this, &GuiDisplay::on_pushbutton_clicked);

然后将GuiDisplay::on_pushbutton_clicked修改为不接受任何参数。您可以通过调用:sender()来获得GuiDisplay::on_pushbutton_clicked中对象的名称,该调用将:

返回一个指向发送信号的对象的指针

然后只需使用QObject::objectName即可获得pb_label:

此属性保存此对象的名称

因此,您只需要将pb_label替换为:sender()->objectName()

直接插槽连接比lambda:更可取的原因有几个:

  1. 它消除了为pb_label存储临时lambda临时QString的需要
  2. 它删除了需要额外堆栈帧的lambda间接寻址
  3. 调试到lambdas并不总是对开发人员友好的,尽管有些IDE可以缓解这个问题
  4. 在Qt 5.2之前,如果lambda插槽所依赖的对象被破坏,则无法断开其连接:

当"接收器"被破坏时,不会自动断开连接,因为它是一个没有QObject的函子。

感谢Piotr Skotnicki和Simple,您的建议都有效。我可能应该进一步阅读,我会在捕获列表中看到[=](参见:Lambda)

因此,我的一行代码可能会导致问题:

connect(pb_ptr, &QPushButton::clicked, [this, pb_label]{on_pushbutton_clicked(pb_label);});

connect(pb_ptr, &QPushButton::clicked, [=]{on_pushbutton_clicked(pb_label);});