首页 > 代码库 > 模块的封装之无伤大雅的形式主义
模块的封装之无伤大雅的形式主义
1 //! \brief macro for inheritance2 #define INHERIT(__TYPE) __TYPE3 #define INHERIT_EX(__TYPE, __NAME) 4 union { 5 __TYPE; 6 __TYPE __NAME; 7 };
[交流][微知识]模块的封装(三):无伤大雅的形式主义
在前面的讨论中,我们介绍了如何在必要的情况下,利用结构体和联合体在C语言中引入应用所必须的一些面向对象的
特性,例如封装、继承、派生以及覆盖。从功能性上来说,前两章的内容足够满足应用了,但从形式上来说,模块的
封装(二)中提供的继承方式在使用的时候还是有那么一点不爽,所以作为弥补一小部分有代码洁癖的人的头缺憾,今
天我们就来讨论一种在不增加任何实质性系统开销的前提下实现对象成员访问形式上的改进。
特别提示,本帖仅用于娱乐,C++档请本着娱乐的心情吐槽和嘲讽,避免引起C档在代码效率上的反驳。形式主义
就是形式主义,没有用就是自己看着舒服
首先我们看看哪里不爽了,举个例子我们定义了一个接口,用于封装一些针对存储器页(Page)的操作:
1 //! \name memory page access interface 2 //! @{ 3 DEF_INTERFACE(i_mem_page_t) 4 5 fsm_rt_t (*PageWrite) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 6 fsm_rt_t (*PageRead) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 7 fsm_rt_t (*PageErase) (mem_t *ptMal, uint32_t wPageAddress); 8 fsm_rt_t (*Erase) (mem_t *ptMal); 9 10 END_DEF_INTERFACE(i_mem_page_t)11 //! @}
这个接口本身没有什么太特别的,就是定义了针对所有已Page为单位的存储器常见的四哥操作:页写,页读,页擦除和
整片擦除。接口类型叫i_mem_page_t,顾名思义就是一个针对memory page 的接口,详细内容不提也罢。接下来我
们又定义了一个接口叫i_mem_t,这个接口视图为存储器抽象一个更通用的操作方式,比如Init open close 之类的为了
方便日后的统一操作,这里不妨也定义成接口 i_mcb_t(这里mcb的意思是 memory control block的缩写),新的接口
i_mem_t就同时继承了 i_mem_page_t 和 i_mcb_t如下所示:
1 //! \name memory control block 2 //! @{ 3 DEF_INTERFACE(i_mcb_t) 4 5 fsm_rt_t (*Init) (mem_t *ptMal, void *ptCFG); 6 fsm_rt_t (*Finish) (mem_t *ptMal); 7 mem_info_t (*Info) (mem_t *ptMal); 8 fsm_rt_t (*Open) (mem_t *ptMal); 9 fsm_rt_t (*Close) (mem_t *ptMal); 10 mem_status_t (*GetStatus) (mem_t *ptMal);11 12 END_DEF_INTERFACE(i_mcb_t) 13 //! @}14 15 //! \name Memory Abstraction Layers16 //! @{17 DEF_INTERFACE(i_mem_t)18 19 i_mcb_t CONTRL; 20 i_mem_page_t PAGE; 21 ...22 23 END_DEF_INTERFACE(i_mem_t)24 //! @}
好的,问题来了: 对于一小部分人来说,下面的代码是有点不可容忍的,套用一句话就是“好焦虑”,好焦虑是把?
不要怀疑,为了了凸显着莫名其妙的焦虑,我用了红色
i_mem_t *ptMEM = xxxxxx;...ptMEM->CONTRL.Open(); //! 打开存储器
这里多按了一个“.”,所谓多按一个点是针对那些有联想功能 的IDE的因为通过上述的方法,所谓的实现接口(Implement)
实际上是通过简单的添加接口的成员变量来实现的,在这种情况下访问继承接口的成员必须要先通过这个成员变量,
比如例子中的CONTROL。对某些人来说上面的例子代码应该支持这种形式才好:
1 i_mem_t *ptMEM = xxxxxx;2 ...3 ptMEM->Open(); //!< 直接打开存储器4 ...5 i_mcb_t *ptMCB = &(ptMEM->CONTRL); //!< CONTRL 仍然存在
简单的来说就是既可以直接访问被继承的接口成员,又可以保留对接口的直接饮用。做到第一点并不难,只要通过下
面的代码就可以了:
1 //! \name Memory Abstraction Layers 2 //! @{ 3 DEF_INTERFACE(i_mem_t) 4 5 fsm_rt_t (*Init) (mem_t *ptMal, void *ptCFG); 6 fsm_rt_t (*Finish) (mem_t *ptMal); 7 mem_info_t (*Info) (mem_t *ptMal); 8 fsm_rt_t (*Open) (mem_t *ptMal); 9 fsm_rt_t (*Close) (mem_t *ptMal); 10 mem_status_t (*GetStatus) (mem_t *ptMal);11 12 fsm_rt_t (*PageWrite) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 13 fsm_rt_t (*PageRead) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 14 fsm_rt_t (*PageErase) (mem_t *ptMal, uint32_t wPageAddress); 15 fsm_rt_t (*Erase) (mem_t *ptMal);16 17 END_DEF_INTERFACE(i_mem_t)
看到这里的一群人就要呵呵了,我也呵呵。你会管这个叫做对接口 i_mem_apge_t 和 i_mcb_t的继承么?你骗谁?
a、如果修改了 i_mcb_t 或者 i_mem_page_t 的内容,我们还需一起修改所有继承了他们的接口,这哪里是面
向接口开发?这简直就是面向麻烦啊,我都不会同意。
b、有些应用就是纯粹的面向接口(虚函数表)的,因此必须要保留对原有接口的引用能力。因此CONTROL和PAGE
是必须要保留的。
想要同时名副其实的继承,又想有能力直接访问被继承接口的成员,就只有借助ANSI标准引入的匿名结构体了,上述的
例子的解决方案:
1 //! \name Memory Abstraction Layers 2 //! @{ 3 DEF_INTERFACE(i_mem_t) 4 5 union { 6 struct { 7 fsm_rt_t (*Init) (mem_t *ptMal, void *ptCFG); 8 fsm_rt_t (*Finish) (mem_t *ptMal); 9 mem_info_t (*Info) (mem_t *ptMal); 10 fsm_rt_t (*Open) (mem_t *ptMal); 11 fsm_rt_t (*Close) (mem_t *ptMal); 12 mem_status_t (*GetStatus) (mem_t *ptMal);13 }; 14 i_mcb_t CONTRL; 15 }; 16 union { 17 struct { 18 fsm_rt_t (*PageWrite) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 19 fsm_rt_t (*PageRead) (mem_t *ptMal, uint32_t wPageAddress, void *ptBuffer); 20 fsm_rt_t (*PageErase) (mem_t *ptMal, uint32_t wPageAddress); 21 fsm_rt_t (*Erase) (mem_t *ptMal);22 }; 23 i_mem_page_t PAGE; 24 }; 25 26 END_DEF_INTERFACE(i_mem_t)27 //! @}
总是觉得那里不对。。。。。尼玛,你又把接口抄了一遍
其实匿名匿名一直都是很强大的比如匿名结构体啊,匿名联合体啊,比如上面的方式可以写成下面形式
1 //! \name Memory Abstraction Layers 2 //! @{ 3 DEF_INTERFACE(i_mem_t) 4 5 union { 6 i_mcb_t; 7 i_mcb_t CONTRL; 8 }; 9 union { 10 i_mem_page_t; 11 i_mem_page_t PAGE; 12 }; 13 14 END_DEF_INTERFACE(i_mem_t)15 //! @}
如果你不想暴露对原有接口的引用你甚至可以这么写
1 //! \name Memory Abstraction Layers2 //! @{3 DEF_INTERFACE(i_mem_t)4 5 i_mcb_t; 6 i_mem_page_t; 7 8 END_DEF_INTERFACE(i_mem_t)9 //! @}
肿么样,强大的cry了吧,接下来我们把形式主义进行到底,首先来定义一个红
//! \brief macro for inheritance#define INHERIT(__TYPE) __TYPE#define INHERIT_EX(__TYPE, __NAME) \ union { __TYPE; __TYPE __NAME; };
然后自然就得到下面的代码
1 //! \name Memory Abstraction Layers2 //! @{3 DEF_INTERFACE(i_mem_t) INHERIT_EX(i_mcb_t, CONTRL) INHERIT_EX(i_mem_page_t, PAGE)4 ...5 ... //! 继承后增加的成员6 ...7 END_DEF_INTERFACE(i_mem_t)8 //! @}
最后我们来谈一个C语言对匿名的支持,因为这个是必要新的特性,很多C编译器并不支持。别的环节我们就不管
了,从事嵌入式开发,常见的几个C编译器比如IAR MDK GCC等都是支持的,但是要编译开关打开。粘贴下面的
代码到你的系统头文件,就可以让你的匿名代码在意思几个环境下获得支持。
1 /* ------------------- Start of section using anonymous unions ------------------ */ 2 #if defined(__CC_ARM) 3 #pragma push 4 #pragma anon_unions 5 #elif defined(__ICCARM__) 6 #pragma language=extended 7 #elif defined(__GNUC__) 8 /* anonymous unions are enabled by default */ 9 #elif defined(__TMS470__)10 /* anonymous unions are enabled by default */11 #elif defined(__TASKING__)12 #pragma warning 58613 #else14 #warning Not supported compiler type15 #endif
模块的封装之无伤大雅的形式主义