C++接口類工程化方法

前陣子隔壁組來了個Rust開發的架構師,討論過如何設計方便易用擴展性高的接口。C++不像Java有接口的概念,可是C++能夠實現接口的功能。下面就總結一下實際項目工程中實現C++接口的方法。php

接口分爲調用接口與回調接口,調用接口主要實現模塊解耦的做用,只要保持接口兼容性,模塊內部的升級對用戶能夠作到無感知。良好的接口分層有助於各業務團隊高效率開發。微信

回調接口主要用於系統有異步事件須要通知用戶。系統預約義接口形式,並由用戶註冊,具體調用時機由系統決定。網絡

調用接口架構

假設有一個網絡發送模塊類Network,類定義以下:異步

class Network{publicbool send();}


虛函數ide

最經常使用的就是虛函數,能夠使用虛函數定義Network接口類:函數

class Network{ public  virtual bool send()=0 static Network* New(); static void Delete(Network* network_);}


將send定義爲虛函數,由繼承類去實現(好比由PLC模塊或者以太模塊繼承),以靜態方法建立子類對象,以基類Network的指針返回給業務使用。資源遵循誰建立誰銷燬的原則,基類還提供Delete方法銷燬對象。由於對象銷燬封裝在接口內部,所以Network接口類能夠不須要虛析構函數。性能

代碼使用虛函數易讀性較高,可是虛函數開銷較大(須要使用虛函數表指針間接調用),沒法在編譯期間內聯優化,而事實上調用接口在編譯期就能肯定使用哪一個函數,不須要用到虛函數的動態特性。此外因爲虛函數使用虛函數表指針間接調用的緣由,增長虛函數會致使函數地址表索引變化,新增接口不能在二進制層面兼容老接口。並且因爲用戶可能繼承了Network接口類,在末尾增長虛函數也有風險,所以虛函數接口一旦發佈上線就基本沒法修改。優化

指向實現的指針ui

能夠使用指向實現的指針來定義Network接口類:

class NetworkImplclass 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{publicIntSerializable(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,使用模板類的繼承,在編譯期實現相似「靜態多態」的功能。更多工程實踐能夠參考《COM本質論》。


本文分享自微信公衆號 - 機械猿(on_ourway)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索