C++闭包二

“多年以前"写过一篇关于C++闭包的文章,现在看来基本是关于lambda的介绍。随着经验的增加,对闭包的理解更加深刻。在上一篇文章中,介绍了我编写的一个tiny_cmdline库,随后我对一些tiny库更感兴趣,便想着做了一个集合,取名为tinylib,其中有一个tiny库是关于match的。调用方式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  using namespace tiny_utility;
  auto match_op = match(
      "1" | [](const auto &i) { ASSERT_EQ(i, "1"); },                              // no line break
      "2" | [](const auto &i) { ASSERT_EQ(i, "2"); },                              // no line break
      "3" | [](const auto &i) { ASSERT_EQ(i, "3"); },                              // no line break
      std::set<std::string>{"5", "6"} | [](const auto &i) { ASSERT_NE(i, "4"); },  // no line break
      placeholder | [](const auto &i) { ASSERT_TRUE(false); }                      // no line break
  );
  // for (const auto &key : std::vector<std::string>{"1", "2", "3", "4" , "5", "6"}) {
  for (const auto &key : std::vector<std::string>{"1", "2", "3", "5", "6"}) {
    match_op(key);
  }

考虑到case key的各种类型兼容,目前的match类是运行时构建的,并且是一个模板类(key的类型是各种各样的)。

如果在实际使用中,比如有某个query接口,是不会考虑在query接口中频繁构建match类的,一般会在构造或者其他初始化接口中构造一次,然后在query接口中多次调用。

但是针对match构造一次的情况,如何保存呢?不可能考虑在类的成员变量中写一长串的类型,并且这样也不适合match的扩展。

用闭包可以解决这问题,闭包的一个特点是可以隐藏一些局部变量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// query.h

class Query {
    // ...
    std::function<void(const std::string &)> match_op_;
};

// query.cpp
#include "tiny_match.h"

Query::Query() {
    auto match_op = match(
        "1" | [](const auto &i) { ASSERT_EQ(i, "1"); },                              // no line break
        "2" | [](const auto &i) { ASSERT_EQ(i, "2"); },                              // no line break
        "3" | [](const auto &i) { ASSERT_EQ(i, "3"); },                              // no line break
        std::set<std::string>{"5", "6"} | [](const auto &i) { ASSERT_NE(i, "4"); },  // no line break
        placeholder | [](const auto &i) { ASSERT_TRUE(false); }                      // no line break
    );
    match_op_ = [match_op](const std::string &key) { match_op(key); };
}

这样,Query类通过保存一个闭包函数,避免在类中声明可变长参数的match类型。

并且,也能发现可以在query.h声明文件中,隐藏tiny_match.h的头文件,只需要在query.cpp中包含即可。

tiny_match.h的实现在: https://github.com/caibingcheng/tinylib/blob/master/tiny_utility/tiny_match.h


根据match这个案例, 再来理解闭包这个概念:

  1. 可以捕获上层函数的局部变量, 比如match_op_捕获了match_op
  2. 可以隐藏局部变量, 比如match_op, 外部通过某些接口访问match_op_时, 是无法感知match_op的存在的
  3. 通过2, 闭包可以隐藏实现细节
  4. 一个"神奇"的通道, 外部接口居然可以访问到内部的局部变量, 尽管是这个局部变量的复制体