加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

如何在编译期进行拓扑排序,自动处理模块初始化依赖顺序。

发布时间:2020-12-13 20:30:40 所属栏目:百科 来源:网络整理
导读:模块之间的初始化和清理的顺序是很重要的。这个顺序应该可以根据各个模块之间的依赖关系求出。而且在绝大部分情况下,链接进工程的各个模块之间的依赖关系在编译期就可以确定出来。下面我们来讨论一下如何通过模板元编程构造一套方便的机制,让编译器自动帮

  模块之间的初始化和清理的顺序是很重要的。这个顺序应该可以根据各个模块之间的依赖关系求出。而且在绝大部分情况下,链接进工程的各个模块之间的依赖关系在编译期就可以确定出来。下面我们来讨论一下如何通过模板元编程构造一套方便的机制,让编译器自动帮你完成初始化和清理的排序工作。

  为了方便大家理解这部分工作到底能够用于处理什么情况,这里先将实现后的用法说明一下:

  假设我们有6个模块,分别叫做ModuleA,ModuleB,ModuleC,ModuleD,ModuleE,ModuleF。这6个模块各自有自己的初始化和清理函数(Init和Shutdown)。在使用这些模块的功能之前必须按照依赖关系进行排序然后初始化,在使用完成之后需要按照相反的顺序进行清理。如果这些顺序都由程序员自己手工控制无疑是容易出错的,为此可以利用模板元的帮助实现一个模块管理器帮助自动完成这项排序工作。

  首先我们来看一下如何描述各个模块之间的依赖关系。在模块管理器中存在如下定义:

  这是一个没有定义的模板结构,该结构的特化可以用于描述每个模块的初始化清理函数以及依赖关系。每个模块都必须各自根据自己的标签特化该结构,并写入自己的内容。

  比如在模块A的头文件ModuleA.h中的模块描述代码如下:

  模板特化利用了一个模块A的标签类(struct ModuleA)进行特化,并且由于模块A依赖于模块D,在dependencies中加入模块D的标签。注意标签是没有必要写出定义的,因为它只是用给编译器在编译器的一个标识,这样编译器根本不会为标签生成任何代码。

dependencies是一个类型链表(TypeList,详见下文)。该类型链表中就保存了A模块的依赖关系。后面两个静态函数Init和Shutdown分别描述了A模块的初始化和清理函数。

  其他的5个模块都按这种方式在各自的头文件中写下自己的依赖项以及初始化和清理函数。

这样在使用的时候就可以方便的利用模板元的模块管理器进行排序,操作代码如下:

  可以看到,这样就可以很方便在利用编译器帮你指定依赖顺序了。而且如果在依赖顺序关系中出现了依赖循环的情况,模板元也可以检测出来,并产生编译错误。这样就是一种很好的机制保证了初始化的清理工作的正确进行。

  下面将详细描述一下整个依赖关系的拓扑排序是如何进行的:

  首先建议参考下Modern C++ Design中对模板元编程的描述,特别是关于类型链表TypeList的实现,因为后面的内容需要对模板元编程有一定的认识。下面我会罗列实现编译期拓扑排序需要用到的东西。一些基本的部分和Modern C++ Design中的内容是相似的,但有一些地方不太相同。代码中的注释上对各个功能函数和结构的用途和算法都写得清楚。因此只在必要的地方进行解释。

  为了方便模板元编程,我们首先需要一些基本的组件。这里用到的基本组件有:

  将bool值映射成类型的模板:BoolToType

  描述将一个空类型NullType,和判断一个类型是否为空类型的模板:IsNullType

  根据一个编译期的bool值进行类型选择的结构Select:

 然后就是最关键的部分,TypeList。下面拓扑排序中需要用到TypeList功能的代码:

  值得注意的是其中实现了一些功能例如ReplaceAll和IsSubList是在Modern C++ Design中没有实现的。而IsSubList在拓扑排序中被用于检查某一项的所有依赖项是否都被解决了。

  基础部分就是这些,下面将讲述拓扑排序的具体执行过程了。在拓扑排序的过程中,使用了两个类型链表:一个是排好序的类型链表,一个是还未排好序的类型链表。

  排序流程是这样的:

  1.遍历整个类型链表,找到第一个满足如下条件的节点i:对于节点i的类型,其依赖的类型链表属于排好序链表的子串(利用上文提出的IsSubList可以执行这个判断,并且如果某一项依赖类型链表为空,则它可以是任何类型链表的子串),记录下这个节点i。

  2.如果i类型是排序链表的子串,则说明i类型所代表的模块之前的依赖项都被处理了,则将i从未排序链表中删除,并放到排序链表中,然后重复1,这个过程将一直持续到未排序链表为空才结束。

  3.如果过程1在遍历整个链表都没有发现任何一个满足条件的节点,说明出现了循环依赖关系,这时候直接返回一个NullType作为错误报告。

  下面是实现该算法的具体代码:

  只要将各个模块的标签放入类型链表中,并调用SortModuleList,它就会自动帮你完成编译期的拓扑排序工作。

  还剩下一个简单的问题是如何根据这个排好序的类型列表正向地执行初始化的工作以及逆向地执行清理工作。如果你上面的都看懂了,那利用模板特化循环一个TypeList实在是太简单了:

这样就实现了编译期拓扑排序.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读