C++ 闭包

关于 C++ Lambda 表达式和闭包的一些思考。

定义

MDN 对闭包的定义为:

函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。

闭包在 JavaScript, Lua 等语言中很常见,它们的函数在每次创建时生成闭包,C++ 在增加 Lambda 表达式以后,也可以较为简单的写出闭包(注意:Lambda 表达式不意味着闭包)。

实例

计数器

在 JavaScript/Lua 中利用闭包写出如下计数器:

1
2
3
4
5
6
7
8
9
let counter = (function () {
let count = 0;
return () => {
return ++count;
};
})();

console.log(counter()); // 1
console.log(counter()); // 2
1
2
3
4
5
6
7
8
9
10
local counter = (function()
local count = 0
return function()
count = count + 1
return count
end
end)()

print(counter()) -- 1
print(counter()) -- 2

类似地,我们在 C++ 中实现

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main() {
auto getCounter = [] {
int count = 0;
return [=]() mutable { return ++count; };
};

auto counter = getCounter();
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
}

可以看到局部变量 countmain 函数中被访问,实现了计数器累加的效果。但是这里还有一个问题:getCounter 函数退栈后,函数中的临时变量会被销毁,那么这个 count 是储存在哪里的?
我们可以输出一下 count 的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main() {
auto getCounter = [] {
int count = 0;
std::cout << &count << std::endl;
return [=]() mutable {
std::cout << &count << std::endl;
return ++count;
};
};

auto counter = getCounter();
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
}

发现输出结果如下:

1
2
3
4
5
0x7fffde240f34
0x7fffde240f54
1
0x7fffde240f54
2

可以发现 count 的地址其实已经发生了变化,Lambda 表达式中的 count 已经不是原来函数里的 count 了,变量是和 Lambda 表达式绑定的。

复制 Lambda 表达式

那么如果复制 Lambda 表达式呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int main() {
auto getCounter = [] {
int count = 0;
return [=]() mutable { return ++count; };
};

auto counter = getCounter();
auto copiedCounter = counter;
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
std::cout << copiedCounter() << std::endl; // 1
}

绑定的参数同样会被复制。

交换参数

实现一个函数 swap,它接受一个 binary 函数,返回交换了此函数两个参数的函数。

1
2
3
4
5
6
7
#include <iostream>

int main() {
auto sub = [](auto x, auto y) { return x - y; };
auto swap = [](auto f) { return [=](auto x, auto y) { return f(y, x); }; };
std::cout << swap(sub)(2, 1); // -1
}

Once

实现一个函数 once,它接受一个函数,并将其返回,返回后的函数只有一次有效调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <type_traits>

int main() {
auto once = [](auto f) {
bool done = false;
return [=](auto &&... args) mutable {
if (done)
throw std::runtime_error("once function cannot be called twice");
done = true;
return f(args...);
};
};
// quick pow
auto f = once([](auto a, auto b, auto mod) {
auto func = [](auto a, auto b, auto mod) {
auto ret = static_cast<decltype(a)>(1);
for (; b; b >>= 1, a = a * a % mod)
if (b & 1) ret = ret * a % mod;
return ret;
};
if constexpr (std::is_same_v<int, decltype(a)> ||
std::is_same_v<unsigned int, decltype(a)>) {
return func(static_cast<std::uint64_t>(a), b, mod);
} else {
return func(a, b, mod);
}
});

try {
std::cout << f(2, 999, 998244353) << std::endl;
std::cout << f(2, 999, 998244353) << std::endl;
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
}

输出为:

1
2
510735315
once function cannot be called twice

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×