今天讲消息分发的一种编译期实现法。
编程是一门非常依赖逻辑的学科,逻辑分为形式逻辑和非形式逻辑,编程就属于形式逻辑。形式逻辑指的是用数学的方式去抽象地分析命题,它有一套严谨的标准和公理系统,对错分明;而日常生活中使用的是非形式逻辑,它不存在标准和公理,也没有绝对的对与错。
根据哲学家大卫·休谟在《人性论》中对于观念之间连接的分类,我们能够把逻辑关系分成三大类:相似关系、因果关系、承接关系。相似关系表示两个组件结构相同,去掉其中一个组件,也只会使功能不够全面,并不会影响程序;因果关系表示两个组件之间依赖性极强,没有第一个组件,就没有第二个组件,第二个组件依赖于第一个组件;承接关系表示两个组件都是局部,只有组合起来,才能构成一个整体。
if else
,你还能写出多少程序?世界是复杂的,问题也是复杂的,因果关系必不可少。if else
,则无法适应动态发展的世界。map
,因作为 key,果作为 value,因是标识符,果是回调函数。由于这种方式发生于运行期,所以也称为动态消息分发。if else
来实现因果逻辑,因为组件与组件之间距离较近,属于同一模块;而网络情境下,一个组件可以瞬间跳跃到距离非常远的另一个组件,这两个组件甚至不在同一台设备上,一台设备可能在上海,另一台在北京,此时如何让这两个组件进行沟通?也就是说,A 组件里面的某个函数执行条件不满足,如何简单地跳到 B、C、D、E…… 这些组件的某个函数中去处理?这种远距离的程序因果逻辑,通过消息分发组件能够非常丝滑地表示。1template <std::size_t N>
2struct string_literal {
3 // str is a reference to an array N of constant char
4 constexpr string_literal(char const (&str)[N]) {
5 std::copy_n(str, N, value);
6 }
7
8 char value[N];
9};
1template <string_literal... Cs>
2struct dispatcher {
3 template <string_literal C>
4 constexpr auto execute_if(char const* cause) const {
5 if (C == cause) handler<C>();
6 }
7
8 constexpr auto execute(char const* cause) const {
9 (execute_if<Cs>(cause), ...);
10 }
11};
Cs
进行表示。如果得到一个具体的「因」,我们需要找到对应的「果」,因此免不了遍历 Cs
,借助 Fold expressions,一行代码优雅地搞定。通过 execute_if
来查找是否存在对应的「因」,也就是对比字符串是否相等,查找到则调用相应的「果」,也就是具体的处理函数 handler<C>()
。1// default implementation
2template <string_literal C>
3inline constexpr auto handler = [] { std::cout << "default effect\n"; };
1// opt-in customization points
2template<> inline constexpr auto handler<"cause 1"> = [] { std::cout << "customization points effect 1\n"; };
3template<> inline constexpr auto handler<"cause 2"> = [] { std::cout << "customization points effect 2\n"; };
1#define _(name) template<> inline constexpr auto handler<#name>
2
3// opt-in customization points
4_(cause 1) = [] { std::cout << "customization points effect 1\n"; };
5_(cause 2) = [] { std::cout << "customization points effect 2\n"; };
1int main() {
2 constexpr string_literal cause_1{ "cause 1" };
3 constexpr dispatcher<cause_1, "cause 2", "cause 3"> dispatch;
4 dispatch.execute(cause_1);
5 dispatch.execute("cause 2");
6 dispatch.execute("cause 3");
7}
dispatch.add_handler(cause, effect)
,因为是成员函数,所以限制了定制地方,必须得在对象所在模块,而静态消息分发这种全局定义特化的方式,则没有这种限制。C == cause
并没有相应的比较操作符,第二是 dispatch.execute(cause_1)
并不能直接传递,因为 char const*
和 string_literal
毕竟不是同一种类型。可以通过添加运算符重载和隐式转换来解决:1template <std::size_t N>
2struct string_literal {
3 // ...
4
5 friend bool operator==(string_literal const& s, char const* cause) {
6 return std::strncmp(s.value, cause, N) == 0;
7 }
8
9 operator char const*() const {
10 return value;
11 }
12
13 // ...
14};
1template <std::size_t N>
2struct string_literal {
3 constexpr string_literal(char const (&str)[N]) {
4 std::copy_n(str, N, value);
5 }
6
7 friend bool operator==(string_literal const& s, char const* cause) {
8 return std::strncmp(s.value, cause, N) == 0;
9 }
10
11 operator char const*() const {
12 return value;
13 }
14
15 char value[N];
16};
17
18// default implementation
19template <string_literal C>
20inline constexpr auto handler = [] { std::cout << "default effect\n"; };
21
22#define _(name) template<> inline constexpr auto handler<#name>
23
24// opt-in customization points
25_(cause 1) = [] { std::cout << "customization points effect 1\n"; };
26_(cause 2) = [] { std::cout << "customization points effect 2\n"; };
27
28
29template <string_literal... Cs>
30struct dispatcher {
31 template <string_literal C>
32 constexpr auto execute_if(char const* cause) const {
33 if (C == cause) handler<C>();
34 }
35
36 constexpr auto execute(char const* cause) const {
37 (execute_if<Cs>(cause), ...);
38 }
39};
40
41int main() {
42 constexpr string_literal cause_1{ "cause 1" };
43 constexpr dispatcher<cause_1, "cause 2", "cause 3"> dispatch;
44 dispatch.execute(cause_1);
45 dispatch.execute("cause 2");
46 dispatch.execute("cause 3");
47}