本篇内容介绍了“C++接口类工程化方法有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
接口分为调用接口与回调接口,调用接口主要实现模块解耦的作用,只要保持接口兼容性,模块内部的升级对用户可以做到无感知。良好的接口分层有助于各业务团队高效率开发。
回调接口主要用于系统有异步事件需要通知用户。系统预定义接口形式,并由用户注册,具体调用时机由系统决定。
调用接口
假设有一个网络发送模块类Network,类定义如下:
class Network{public:bool send();}
虚函数
最常用的就是虚函数,可以使用虚函数定义Network接口类:
class Network{ public: virtual bool send()=0 static Network* New(); static void Delete(Network* network_);}
将send定义为虚函数,由继承类去实现(比如由PLC模块或者以太模块继承),以静态方法创建子类对象,以基类Network的指针返回给业务使用。资源遵循谁创建谁销毁的原则,基类还提供Delete方法销毁对象。因为对象销毁封装在接口内部,因此Network接口类可以不需要虚析构函数。
代码使用虚函数易读性较高,但是虚函数开销较大(需要使用虚函数表指针间接调用),无法在编译期间内联优化,而事实上调用接口在编译期就能确定使用哪个函数,不需要用到虚函数的动态特性。此外由于虚函数使用虚函数表指针间接调用的原因,增加虚函数会导致函数地址表索引变化,新增接口不能在二进制层面兼容老接口。而且由于用户可能继承了Network接口类,在末尾增加虚函数也有风险,因此虚函数接口一旦发布上线就基本无法修改。
指向实现的指针
可以使用指向实现的指针来定义Network接口类:
class NetworkImpl;class Network{ public: bool send(); static Network* New(); Network() ~Network(); private: NetworkImpl* impl;}
Network的具体实现由NetworkImpl完成,通过使用指向实现的指针的方式来定义接口,接口类对象的创建和销毁可以由用户负责,更好的管理对象生命周期。
此外该方法通用性强,新增接口不会影响二进制兼容性,有利于项目快速迭代。
但是该方法还是增加了一层调用,对性能还是略微有影响,不符合C++的零开销原则。
隐藏的子类
隐藏的子类思想很简单,接口要实现的目标就是解耦,主要就是隐藏实现,也就是隐藏接口类的成员变量。如果能将接口类的成员变量都转移到另一个隐藏类中,那么接口类就不需要任何成员变量,那么就达到了隐藏实现的目的。具体实现方法如下:
class Network{ public: bool send(); static Network* New(); static void Delete(Network* network_); protected: Network(); ~ Network();}
Network接口类只有成员函数,没有成员变量。提供静态方法New创建对象,Delete方法销毁对象。New方法的实现中会创建隐藏的子类NetworkImol的对象,并以父类Network指针的形式返回。NetworkImol类中定义了Network类的成员变量,并将Network类声明为friend:
class NetworkImol:public Network{ friend class Network ; private: // 定义Network类的成员变量}
Network类的实现中创建NetworkImol子类对象,并以父类指针形式返回,通过将this强制转换为子类NetworkImol类型的指针来访问成员变量:
bool Network::send(){ NetworkImpl* impl = (NetworkImpl*)this; //通过impl访问成员变量,实现Network的功能}static Network* New(){ return new NetworkImpl();}static Delete(Network* network){ delete (NetworkImpl*)network;}
该方法符合C++零开销原则,且同样符合二进制兼容性。
Rust语言中有一种Trait功能,可以在类外面实现一个Trait(不需要修改类代码),那么C++同样可以参考实现Trait功能假设需要在Network类中实现发送序列化数据,重新设计Network接口,Serializable类定义如下:
class Serializable{public: virtual void serialize()const =0;};
Network类定义如下:
class Network{public: bool send(const char* host, uint16_t port,constSerializable& buf);}
Serializable接口类似于Rust中的Trait,现在任何实现了Serializable接口类的对象都可以通过调用Network类接口完成数据发送功能。那么问题来了,加入项目迭代需要增加通过Network类发送int型数据,如何在不修改类定义的同时实现Serializable接口呢?很简单:
class IntSerializable :public Serializable{public:IntSerializable(const int i):intthis(i){}virtual void serialize() const override{ buffer += std::to_string(*intthis);}private: const int* const intthis;};
之后就可以通过Network发送int型数据了:
Network* network = Network::New();int i=1;network->send(ip,port, IntSerializable(i));
非侵入式接口将类和接口区分开来,类中的数据只包含成员变量,不包含虚函数表指针,因此类不会因为实现了n个接口而引入n个虚函数表指针。而接口中只包含虚函数表指针,不包含数据成员,类和接口之间通过实现类进行类型转换,类只有在充当接口使用的时候才会引入虚函数表指针,不充当接口的时候不会引入,符合C++零开销原则。
Rust编译器通过impl关键字记录了每个类实现了哪些Trait,因此在赋值时编译器可以自动完成将对象转换为对应的Trait类型。而g++等C++编译器并没有记录这些转换信息,因此需要手动转换类型。本质上还是通过代码帮编译器记录每个接口类实现了哪些Trait,使用模板类的继承,在编译期实现类似“静态多态”的功能。
“C++接口类工程化方法有哪些”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。