前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >QT5和C++ 11:Lambda是你的朋友(翻译文)

QT5和C++ 11:Lambda是你的朋友(翻译文)

作者头像
Qt君
发布2019-07-15 14:52:28
1.6K0
发布2019-07-15 14:52:28
举报
文章被收录于专栏:跟Qt君学编程

前言

自从Qt5发布以来,我一直在推迟对我一直在做的一个项目升级到Qt5。即使是像这样的版本,从Qt 4.7到Qt 5(没错,跳过了4.8),出于某种原因也不像暗示的那么简单。他们说: “只需改变包含和链接路径, 就会自行编译。” Psht,是正确的。别再上当了。

在我使用Qt工作多年之后,我实现了飞跃,获得巨大的进步.我觉得C++和Qt现在是一起工作的,而不是仅仅帮助您开发更好的C++。我相信信号/槽机制已经在c++ 11 lambda函数中找到了它的灵魂伴侣。

这个信号/槽到底是什么?

如果不使用Qt, 你可能根本就不在乎,但是Qt框架中对象之间的基本通讯机制是由信号(可以发出的事件)和槽(事件处理程序)定义的。

作为快速介绍,使用以下示例(假设setupUi方法创建了三个QPushButton对象和一个QTextEdit对象):

代码语言:javascript
复制
class AMainWindow : public QWidget, public Ui::AMainWindow
{
Q_OBJECT
public:
   explicit AMainWindow(QWidget *parent = 0) : QWidget(parent)
   {
      // create the widget interface (add the buttons and text box)
      this->setupUi(this);

      // connect the signal/slots
      connect(pushButton, SIGNAL(clicked()), this, SLOT(SetTextOne()));
      connect(pushButton_2, SIGNAL(clicked()), this, SLOT(SetTextTwo()));
      connect(pushButton_3, SIGNAL(clicked()), this, SLOT(SetTextThree()));
}

public slots:
   void SetTextOne(void)
   {
      textEdit->setText("bonjour");
   }

   void SetTextTwo(void)
   {
      textEdit->setText("comment allez vous");
   }

   void SetTextThree(void)
   {
      textEdit->setText("pas trop mal, et vous?");
   }
};

我们已经将每一个按钮的点击信号链接到这里定义的三个方法上。

在connect方法调用中使用的SIGNAL和SLOT是连接函数名称的宏,出于我们的目的,先让我假设它是魔法。

那么,信号/槽机制有什么问题?

这个没有什么损坏,对吧? 它的工作原理…我猜。我前面谈到的信号/槽宏“魔法”并不是那么神奇。这两个宏实际上都解析为一个字符串。

问题1:

它使用字符串在运行时解析连接。所以,如果你碰巧有一个槽,它接受一个字符串,而信号声明接受一个int,但你不知道它,直到你运行你的应用程序。除非您习惯JavaScript开发,否则这可能会让您措手不及。

问题2:

为什么我必须定义三个方法来做基本相同的事情?

在Qt5前

在Qt5和c++ 11之前,我们可以用QSignalMapper类来做这样的事情:

代码语言:javascript
复制
class AMainWindow : public QWidget, public Ui::AMainWindow
{
   Q_OBJECT
public:
   explicit AMainWindow(QWidget *parent = 0) : QWidget(parent)
   {
       // create the widget interface
       this->setupUi(this);

       // use the QSignalMapper to pass custom string for each button
       // to a single slot
       QSignalMapper* mapper = new QSignalMapper();
       connect(mapper, SIGNAL(mapped(QString)), this, SLOT(SetText(QString)));

       connect(pushButton, SIGNAL(clicked()), mapper, SLOT(map()));
       mapper->setMapping(pushButton, "bonjour");

       connect(pushButton_2, SIGNAL(clicked()), mapper, SLOT(map()));
       mapper->setMapping(pushButton_2, "comment allez vous");

       connect(pushButton_3, SIGNAL(clicked()), mapper, SLOT(map()));
       mapper->setMapping(pushButton_3, "pas trop mal, et vous?");

   }

public slots:
   void SetString(QString text)
   {
       textEdit->setText(text);
   }
};

使用QSignalMapper类我们去掉了三个槽函数,它们对不同的文本做了基本相同的操作,并用一个函数替换了它。但是你我都知道这感觉有点老套。QSignalMapper就像是一个真正问题的补丁。

添加C++ lambda函数

如果您一直关注c++的发展,您可能知道lambda函数和表达式。如果您不熟悉它们,可以通过搜索找到大量信息,但简单的回答是,它们基本上是内联的、未命名的函数。一般格式为:

代码语言:javascript
复制
[capture](parameters) { body };

其中capture指定在函数声明的作用域中哪些可见的符号对lambda的主体是可见的,parameters是传递给lambda的参数列表,而body是函数的定义。

这对Qt意味着什么?

要考虑的最重要的事情是,它们可以用作槽的函数指针。我们可以像这样连接一个槽:

代码语言:javascript
复制
connect(<pointer to source object>, <pointer to signal>, 
        <pointer to slot function>);

首先,请注意,我们现在可以将实际指针传递到信号和槽,而不是仅仅使用信号和槽宏(如果需要,您可以仍然可以使用这些宏)。这意味着对connect的连接是在编译时期检查。不再运行程序并发现您使用了int作为槽,但是信号传递了一个字符串。

其次,lambda基本上就是一个函数指针。现在考虑一下这如何改变我们的示例:

代码语言:javascript
复制
class AMainWindow : public QWidget, public Ui::AMainWindow
{
   Q_OBJECT
public:
   explicit AMainWindow(QWidget *parent = 0) : QWidget(parent)
   {
       // create the widget interface
       this->setupUi(this);

       // connect the signal to lambda (using [=] to capture variables 
       // used in the body by value)
       connect(pushButton, &QPushButton::clicked,
               [=]() { this->SetString("bonjour"); });
       connect(pushButton_2, &QPushButton::clicked,
               [=]() { this->SetString("comment allez vous"); });
       connect(pushButton_3, &QPushButton::clicked,
               [=]() { this->SetString("pas trop mal, et vous?"); });
   }

public slots:
   void SetString(QString text)
   {
       textEdit->setText(text);
   }
};

注意: 如果您在Mac上使用Clang,您可能需要在.pro文件中添加“CONFIG += c++11”来启用c++11特性来支持lambda函数。

上面例子与使用QSignalMapper比较。它只是更简洁,更容易理解。然而,我们不需要就此打住。由于我们的槽函数非常简单,而且真正重要的东西(我们正在设置的字符串)是在lambda中,所以甚至没有理由拥有它。

代码语言:javascript
复制
class AMainWindow : public QWidget, public Ui::AMainWindow
{
   Q_OBJECT
public:
   explicit AMainWindow(QWidget *parent = 0) : QWidget(parent)
   {
       // create the widget interface
       this->setupUi(this);

       // connect the signal to lambda (using [=] to capture variables
       // used in the body by value)
       connect(pushButton, &QPushButton::clicked,
               [=]() { textEdit->setText("bonjour"); });
       connect(pushButton_2, &QPushButton::clicked,
               [=]() { textEdit->setText("comment allez vous"); });
       connect(pushButton_3, &QPushButton::clicked,
               [=]() { textEdit->setText("pas trop mal, et vous?"); });
   }
};

这是一个比我们原来有三个槽的类更优雅的解决方案。

附加说明

当然,并非一切都是完美的。在使用指向函数的指针和lambdas作为槽时,有一些事情需要记住。首先,它有点复杂,因为您必须指定slot类的完整类型(如果您不使用lambda),但是较少的模糊性不会影响到任何人。

但是有两个更大的问题:

(1) 函数指针和连接时不支持默认参数;

(2) 使用lambdas创建的槽在‘receiver’销毁时不会自动断开。

第二个问题不一定是主要问题,因为使用lambda函数的事实表明您并不打算经常连接/断开连接(尽管仍然可以手动连接)。然而,第一个问题可能是很烦人的。考虑这样的写法:

代码语言:javascript
复制
connect(fromObject, &ASomeClass::someSignal, 
        toObject, &AAnotherClass::slotHandler);

其中信号和槽定义为:

代码语言:javascript
复制
void ASomeClass::someSignal(bool arg = true);
void AAnotherClass::slotHandler() { cout << “handled”; }

即使参数在考虑默认参数时匹配,connect方法也会抛出编译时错误。当然,一个简单的修复方法是这样使用lambda并且忽略信号参数:

代码语言:javascript
复制
connect(object, &ASomeClass::someSignal, 
        [=](bool arg) { toObject->slotHandler(); });

原文作者:

Brian Poteat;

原文链接:

https://artandlogic.com/2013/09/qt-5-and-c11-lambdas-are-your-friend/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Qt君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档