当Qt(C++)中,function成为first-class

我对C++的使用和理解是不断变化的。从一开始的C with Class 到接触到设计模式,才理解了什么叫真正的OOP;从接触到STL才真正正视Template,了解了什么是GP;从Python和Golang的火热中了解了函数作为first-class的力量;从某些文章对Lisp近乎玄学的推崇中知道了FP的优势和它逐渐在主流的编程方式中兴起的原因。
C++是包容和自由的,自从学了std::function和lambda之后,我也开始逐渐学着向FP方式转变。因为在很多方面,将function作为first-class,对编码带来的不只是形式上的变化,更是思维方式的变化。

lambda与QObject::Connect

C++11lambda表达式和Qt5的搭档,使得可以可以放弃SIGNALSLOT宏,采用一种更加直观和简洁的方式使用Connect。
使用lambda之前,如果要在Qt的main函数里使用slot,不得不另外构造一个类继承QObject,并且定义槽函数,然后才能在main里实例化对象并绑定槽函数:

//myObject.h
#include <QObject>
class myObject : public QObject {
public:
    myObject(QObject* parent = 0);
    ~myObject();

public slots:
    void onClicked();
};

//myObject.cpp
......
void myObject::onClicked(){
    qDebug() << "clicked";
}

//main.cpp
#include <QApplication>
#include <QDebug>
#include <QPushButton>
#include "myObject.h"
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton *button = new QPushButton("click");
    button->show();
    myObject *obj = new myObject();
    QObject::connect(button, SIGNAL(clicked()), obj, SLOT(onClicked()));
    return app.exec();
}

为了一个槽函数,还要引入另一个类,实在是大动干戈,而且还不直观(这可能也是OOP为人诟病的一个方面吧)。在有了lambda之后,是这么干的:

#include <QDebug>
#include <QPushButton>
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton *button = new QPushButton("click");
    button->show();
    QObject::connect(button, &QPushButton::clicked, []() {
        qDebug() << "clicked";
    });
    return app.exec();
}

简洁!一目了然!!

虚函数与std::function

上面的例子其实还有类似的情况,在QWidget上想要自定义鼠标press事件,我们有两种方式:

  • 该widget外部使用eventfilter来拦截该widget的鼠标press事件并处理
  • 自定义继承自QWidget的widget类并覆盖其mousePressEvent()的虚函数

第一种方式不直观,对该控件的处理要到别的地方去寻找,不是很“OOP”;第二种方式和上一例子一样,代价有点大。
其实这样的例子还有很多。传统的OOP在解耦的同时一定会导致体型的臃肿,除此之外还经常会有将处理流程隐藏在层层的封装和继承之中导致的不直观不清晰的问题。
代码说到底是人来写人来读的,任何反直观的都是不好的。编程就像写文章,诘屈聱牙的东西没人愿意看,好的代码一定是读起来酣畅淋漓的。
如果我们自己实现一个Button类,可以是这样的:

//Button.h
class Button {
    ......
    virtual void onClicked() = 0;
};

//MyButton.h
class MyButton : public Button {
    ......
    void onClicked(){
        //需要的操作
    }
};

这样,在我们需要一个Button的时候,新写一个类继承Button,将点击的处理写在onClicked方法内即可。可以,这很“OOP”。
现在呢,我们可以利用std::function,使得函数作为类成员,像对待类的普通成员一样对函数成员进行赋值操作,即可得到我们需要的对象:

//Button.h
class Button {
    ......
    std::function<void()> _onClicked;
};

//在使用的地方
Button btn;
btn._onClicked = [](){
    //需要的操作
};

喏,更加的简单明了。函数不需要通过继承来特化,而是通过像普通变量一样的方式直接实例化,带来的不光是结构 上的简单,还有语意上的直白。

ScopeGuard

资源的释放从来都是一个问题。文件句柄、锁、等等资源,申请的时候我们可能想着一会儿用完要释放,等到用完之后可能就忘了,或者是因为分支处理漏掉了,这都是很有可能发生的,就算没有在分支处理中漏掉,在各个分支里都重复的写同样的释放资源的代码也很不fashion,没人愿意当CV战士。
利用RAII特性,局部变量析构时候释放资源已经成为一个通用做法。麻烦的是我们需要为各种资源都创建类来利用其析构函数释放资源,太麻烦。现在有了std::function就好了,借鉴一下std::lock_guard,就有了下面的做法:

//ScopeGuard.h
class ScopeGuard {
public:
    explicit ScopeGuard(std::function<void()> callback)
    : _onExit(callback) {};

    ~ScopeGuard(){
        _onExit();
    };

private:
    std::function<void()> _onExit;
};
#define ON_SCOPE_EXIT(callback) ScopeGuard EXIT##__LINE__(callback)

//在使用的地方
{
    HANDLE f = fopen("conf.yaml");
    ON_SCOPE_EXIT([=](){
        fclose(f);
    });       

    ......
    {
        _mutex.lock();
        ON_SCOPE_EXIT([&](){
            _mutex.unlock();
        });

        ......
    }

    ......
}

资源创建之后,立即跟在后面写释放方式,不会忘不会漏,看起来还明确。
ON_SCOPE_EXIT宏作用在于创建了一个ScopeGuard局部变量;变量名由行号确定,避免了多个ScopeGuard重名的问题。

其它

将function作为first-class带来的改变还有很多,比如将function保存在容器中,将function作为值传递给别的线程执行,返回闭包来创建累加器等等。

初极狭,才通人。复行数十步,豁然开朗。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容