您的位置 首页 模拟

你了解Linux渠道相关代码的C++解决方案

你了解Linux平台相关代码的C++解决方案-由于程序中不可避免的存在平台相关代码(系统调用等),软件研发人员为了保证自己的产品在各个 Linux 平台上运行顺畅,一般都需要在源代码中大量使用预编译参数,这样会大大降低程序的可读性和可维护性。

本文首要提出渠道相关代码形成的两个问题,然后针对这两个问题按部就班顺次提出处理计划,在剖析了前两个计划缺陷的基础上,最终侧重介绍一种依据多种规划办法的 Linux 渠道相关代码的处理计划,并给出此计划的 C++ 完结。

Linux 渠道相关代码带来的问题

现在市场上存在着许多不同的 Linux 渠道(例如:RedHat, Ubuntu, Suse 等),各大厂商和社区都在针对自己支撑的渠道进行优化,为运用者带来许多便利的一同也对软件研制人员在进行编码时带来不少问题:

由于程序中不可防止的存在渠道相关代码(体系调用等),软件研制人员为了确保自己的产品在各个 Linux 渠道上运转顺利,一般都需求在源代码中许多运用预编译参数,这样会大大下降程序的可读性和可保护性。

接口渠道无关性的准则是研制人员有必要遵从的准则。可是在处理渠道相关代码时假如处理不妥,此准则很有或许被损坏,导致不良的编码风格,影响代码的扩展和保护。

本文将针对这两个问题按部就班顺次提出处理计划。

经过设置预编译选项来处理渠道相关代码

经过为每个渠道设置相关的预编译宏能够处理 Linux 渠道相关代码的问题,实践情况下,许多软件开发人员也乐于独自运用这种办法来处理问题。

假定现有一动态库 Results.so,SomeFunction() 是该库的一个导出函数,该库一同为 Rhel,Suse,Ubuntu 等三个渠道的 Linux 上层程序服务。(后文比如均依据此例并予以扩展。)

清单 1. 设置预编译选项示例代码如下:

// Procedure.cpp void SomeFuncTIon() { //Common code for all linux …… …… #ifdef RHEL SpecialCaseForRHEL(); #endif #ifdef SUSE SpecialCaseForSUSE(); #endif #ifdef UBUNTU SpecialCaseForUBUNTU(); #endif //Common code for all linux …… …… #ifdef RHEL SpecialCase2ForRHEL(); #endif #ifdef SUSE SpecialCase2ForSUSE(); #endif #ifdef UBUNTU SpecialCase2ForUBUNTU(); #endif //Common code for all linux …… …… } IT网,http://www.it.net.cn

开发人员能够经过设置 makefile 宏参数或许直接设置 gcc 参数来操控实践编译内容。 IT网,http://www.it.net.cn

例如:

gcc -D RHEL Procedure.cpp -o Result.so -lstdc++ // Use RHEL marco

SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 别离在该库 (Results.so) 的其他文件中予以完结。

图 1. 清单 1 代码的结构图

带来的问题

SomeFuncTIon() 函数代码冗余,格局紊乱。本例仅触及三个预编译选项,但实践情况中由于 Linux 版别许多并且或许触及操作体系位数的问题,增加对新体系的支撑会导致预编译选项不断增多,形成 SomeFuncTIon() 函数结构非常紊乱。

新增其他渠道相关接口(例如:增加 SpecialCase3ForRHEL(),SpecialCase3ForSUSE(),SpecialCase3ForUBUNTU),会成倍增加代码中预编译宏的数量。

损坏了接口渠道无关性的准则。SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 仅仅同一功用各个渠道的不同完结,归于封装内容,不应该分隔露出给调用者。

可见,简略运用预编译宏来处理渠道相关代码发生的问题不是一个好的办法,并没有处理本文开端提出的两个问题。后文将经过三个计划顺次处理这些问题。

处理计划 1:依据接口渠道无关性准则进行优化

实质上,SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU() 仅仅同一功用在不同渠道上的完结,SpecialCase2ForRHEL(),SpecialCase2ForSUSE(),SpecialCase2ForUBUNTU() 亦如此。关于调用者,应该遵从接口渠道无关性的准则,运用一致的接口进行调用,这样才干简化代码,使代码易于保护。

清单 2. 处理计划 1 示例代码如下:

// Procedure.cpp void SomeFuncTIon() { //Common code for all linux …… …… SpecialCase(); //Common code for all linux …… …… SpecialCase2(); //Common code for all linux …… …… } void SpecialCase() { //Common code for all linux …… …… #ifdef RHEL SpecialCaseForRHEL(); #endif #ifdef SUSE SpecialCaseForSUSE(); #endif #ifdef UBUNTU SpecialCaseForUBUNTU(); #endif //Common code for all linux …… …… } void Special2Case() { //Common code for all linux …… …… #ifdef RHEL SpecialCase2ForRHEL(); #endif #ifdef SUSE SpecialCase2ForSUSE(); #endif #ifdef UBUNTU SpecialCase2ForUBUNTU(); #endif //Common code for all linux …… …… }

此计划的长处:

遵从了接口渠道无关性准则,相同的功用只供给一个接口,每个渠道的完结归于完结细节,封装在接口内部。此计划供给了必定的封装性,简化了调用者的操作。

此计划的缺陷:

预编译宏众多的问题依然没有处理,每次新增功用函数,就会成倍增加预编译宏的数量。相同每次增加对已有功用新渠道的支撑,也会不断增加预编译宏的数量。

可见,此计划部分处理了本文开端提出的两个问题中的一个,但仍有问题需求持续处理。

处理计划 2: 经过分层对进行优化

换一个视点来考虑,能够在二进制层面临渠道相关代码进行优化。经过对库的结构进行分层来优化,为每个 Linux 渠道供给独自的完结库,并且把调用端独立提取出来。如下图所示:

图 2: 计划 2 的结构图

此计划独自将调用端笼统出来,将每个渠道完结端的相关代码提取出来做成一个独自的库(Rhel.so,Suse.so,Ubuntu.so)。SpecialCase() 为同一功用在不同渠道的完结,选用相同接口名。底层库需求与 Results.so 库一同发布,也就是说,Redhat 版别发布时需一同打包 Results.so 和 Rhel.so,其他版别亦然。 Linux学习,http:// linux.it.net.cn

此计划的长处:

处理了预编译宏众多的问题,经过二进制分层能够将代码里的一切预编译选项去掉。遵从了接口渠道无关性的准则。

可见,此计划很好地处理了本文开端提出的两个问题。 Linux学习,http:// linux.it.net.cn

此计划的缺陷:

每次发布 Results.so 的时分,底层库需求随同一同发布,导致可执行包文件数量成倍增加。并且许多小程序,小工具的发布往往采纳独自的二进制文件,不允许有底层库的存在。

处理计划 3: 结合署理办法,桥接办法和单例办法进行优化

现在针对原始问题持续进行优化,抛弃计划 2 选用的分层办法,在单一库的范围内运用 C++ 多态特性和规划办法进行优化。

方针作用:

源代码中尽或许削减预编译选项呈现的频率,防止因功用扩展和渠道支撑的增加导致预编译宏数量爆破。

彻底遵从接口渠道无关性的准则。

清单 3. 处理计划 3 调用端示例代码如下:

// Procedure.cpp void SomeFunction() { //Common code for all linux …… …… XXHost::instance()->SpecialCase1(); //Common code for all linux …… …… XXHost::instance()->SpecialCase2(); //Common code for all linux …… …… } Linux学习,http:// linux.it.net.cn

图 3:计划 3 的详细完结类图

此计划结合改善的署理办法(Proxy),桥接办法(Bridge)和单件办法(Singleton),并运用 C++ 封装、承继和多态特性予以完结。 IT网,http://www.it.net.cn

IHost 是顶层笼统接口类,声明晰完结端需求完结的功用函数以及调用端需求调用的接口函数。 Linux学习,http:// linux.it.net.cn

图 3 右半部分派生自 IHost 的各个类为完结端,在完结端, Linux学习,http:// linux.it.net.cn

为每个 Linux 体系独自完结了一个类,相互之间无关联性。该类完结了该操作体系渠道相关的功用(SpecialCase1() 和 SpecialCase2()),即完结了渠道相关代码。每个完结类采纳单件办法。

Init() 和 terminate() 用来初始化和整理操作。Init() 函数首要创立自己(单件办法),其次创立左边署理类(单件办法,见下段描绘),最终将自己的内存地址经过 SetHost() 函数交给左边署理方。

图 3 左半部分派生自 Host 的各个类为调用端,在调用端, IT网,http://www.it.net.cn

Host 类做了一层封装,RhelHost 等派生类为实践的署理者(调用者),每个 Host 的派生类别离代表一种需求(调用方),是右侧完结类的一个署理,例如 RhelHost 是 RhelOS 的署理,SuseHost 是 SuseOS 的署理,UbuntuHost 是 UbuntuOS 的署理。每个 Host 的派生类采纳单件办法。 IT网,http://www.it.net.cn

Host 类和 HostImp 类之间选用桥接的规划办法,运用 C++ 多态特性,最终经过 HostImp 类调用完结端类的完结。调用端的调用进程如下:

经过 RhelHost 的指针调用 SpecialCase(),由于 RhelHost::SpecialCase() 没有掩盖基类虚函数的完结,实践调用的是 Host::SpecialCase()。

Host 的一切调用被桥接到 HostImp 对应的函数。

由 HostImp 类调用确认的完结端的某一个目标的对应完结函数(HostImp 类的 SetHost() 函数记录了右侧类的目标内存地址)。

清单 4. 处理计划 3 结构首要源代码如下:

// Host.h class IHost { public: virtual void SpecialCase1() = 0; virtual void SpecialCase2() = 0; }; class Host : public IHost { public: virtual ~Host() {}; void setHost(IHost* pHost) { m_pImp->setHost(pHost); } virtual void SpecialCase1() { m_pImp->SpecialCase1(); }; virtual void SpecialCase2() { m_pImp->SpecialCase2(); }; protected: Host(HostImp * pImp); private: HostImp* m_pImp; friend class HostImp; }; class RhelHost : public Host { public: static RhelHost* instance(); private: RhelHost(HostImp* pImp); }; RhelHost * RhelHost::instance() { static RhelHost * pThis = new RhelHost (new HostImp()); return pThis; } RhelHost:: RhelHost (HostImp* pImp) : Host(pImp) { } class RhelOS : public IHost { public: static void init() { static RhelOS me; RhelHost::instance()->setHost(&me); } static void term() { RhelHost::instance()->setHost(NULL); } private: virtual void SpecialCase1() { /* Real Operation */ }; virtual void SpecialCase2() { /* Real Operation */ }; }; // HostImp.h class HostImp : public IHost { private: HostImp(const HostImp&); public: HostImp(); virtual ~HostImp() {}; void setHost(IHost* pHost) { m_pHost = pHost; }; virtual void SpecialCase1() { if(m_pHost != NULL) m_pHost->SpecialCase1() } virtual void SpecialCase2() { if(m_pHost != NULL) m_pHost->SpecialCase2() } private: IHost* m_pHost; };

此计划的长处:

遵从接口渠道无关性准则。此计划将各渠道通用接口提升到最高的笼统层,易于了解和修正。

最大极限地下降预编译选项在源代码中的运用,实践上,本例中只需求在一处运用预编译宏,示例代码如下:

void Init() { #ifdef RHEL RhelOS::init(); #endif #ifdef SUSE SuseOS::init(); #endif #ifdef UBUNTU UbuntuOS::init(); #endif }

源代码其他地方不需求增加预编译宏。

完结端和调用端都经过类的办法进行封装,并且完结端类和调用端类都能够自己独自扩展,完结一些各自需求完结的使命,所要保持一致的仅仅接口层函数。扩展性和封装性很好。

由此可见,此计划很好地处理了本文开端提出的两个问题,并且代码结构明晰,可保护型好。

接下来对上述源代码持续进行优化。上例 SuseHost/UbuntuHost/SUSEOS/UBUNTUOS 等类的完结被省略,实践上这些类的完结与 RhelHost 和 RHELOS 类似,能够运用宏来进一步优化结构代码结构。 Linux学习,http:// linux.it.net.cn

清单 5. 处理计划 3 结构首要源代码优化:

#define HOST_DECLARE(name) \ class ##nameHost : public Host \ { \ public: \ static ##nameHost* instance(); \ private: \ ##nameHost(HostImp* pImp); \ }; #define HOST_DEFINE(name) \ ##nameHost* ##nameHost::instance() \ { \ static ##nameHost* pThis = new ##nameHost(new HostImp()); \ return pThis; \ } \ ##nameHost::##nameHost(HostImp* pImp) \ : Host(pImp) \ { \ } #define HOST_IMPLEMENTATION(name) \ class ##name##OS : public IHost \ { \ public: \ static void init() \ { \ static ##name##OS me; \ ##nameHost::instance()->setHost(&me); \ } \ static void term() \ { \ ##nameHost::instance()->setHost(NULL); \ } \ private: \ virtual void SpecialCase1(); \ virtual void SpecialCase2(); \ };

运用三个宏来处理类似代码。至此,优化完结。从源代码视点来剖析,作为完结端的开发人员,只需求三步就能够完结操作:

调用 init() 函数;

运用 #define HOST_IMPLEMENTATION(name);    例如:#define HOST_IMPLEMENTATION(DEBIAN)

完结 DEBIAN::SpecialCase1() 和 DEBIAN::SpecialCase2()。    作为调用端的开发人员,也只需求三步就能够完结操作:

运用 #define HOST_DECLARE(name) 进行声明;    例如 : #define HOST_DECLARE(DEBIAN)

运用 #define HOST_DEFINE(name) 进行界说;    例如: #define HOST_DEFINE (DEBIAN)

调用接口。    例如: DEBIANHost::instance()->SpecialCase1();DEBIANHost::instance()->SpecialCase2();

可见,优化后计划的代码明晰,不失为一个杰出的渠道相关代码的处理计划。

由于调用端和完结端往往需求传递参数,能够经过 SpecialCase1() 函数的参数来传递参数,相同的这个参数类能够经过桥接的办法予以完结,本文不再胪陈,读者能够自己测验。

对计划 3 的扩展

扩展 1:对单一操作体系多对多的扩展

关于计划 3 的完结,或许有读者会问,调用端只需求 Host 类不需求其派生类即可完结计划 3 中的功用,确实如此,由于计划 3 中的署理类一直是1对1的联系,即 RhelHost 署理 RhelOS,Redhat 下只存在这1对1的联系。可是实践情况下,单一体系下很或许存在多对多的联系。

例如,在单一操作体系中,或许需求一同完结多种风格的窗口。实践上,变成了多对多的署理联系。

图 4:单一操作体系不同 c 风格窗口的完结类图

Style1Host 署理 Style1Dialog,Style2Host 署理 Style2Dialog,Style3Host 署理 Style3Dialog,三个窗口一同并存,也就是说左边三个完结类的实例和右侧三个署理类的实例一同存在。可见,计划 3 的规划办法扩展性杰出,完结端和调用端都能够在遵从接口不变性的情况下独自扩展自己的功用。

扩展 2:对多操作体系的扩展

计划 3 不只能够针对 Linux 渠道相关代码进行处理,还能够扩展到其他许多场合。例如,现在的程序库往往需求针对多个操作体系,包含 Windows, Linux, Mac。每个操作体系往往运用不同的 GUI 库,这样在完结窗口操作的时分必定触及到渠道相关代码。相同能够用计划 3 予以完结。

图 5:多操作体系的完结类图

Linux学习,http:// linux.it.net.cn

总结

本文开端提出渠道相关代码形成的两个问题,接着按部就班提出处理计划。在剖析了常用的设置预编译选项办法的利害的基础上,提出了一种新的运用 C++ 多态特性,结合运用署理办法,桥接办法和单件办法处理渠道相关代码的计划,并对这一计划予以扩展,给读者供给了一种新的高效的处理渠道相关代码的办法。 Li

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/zhishi/moni/88767.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部