爲了驗證頭文件設計的自知足原則,實現文件的第一條語句必然是包含其對應的頭文件。html
反例:程序員
// cppunit/TestCase.cpp #include "cppunit/core/TestResult.h" #include "cppunit/core/Functor.h" // 錯誤:沒有放在第一行,沒法校驗其自知足性 #include "cppunit/core/TestCase.h" namespace { struct TestCaseMethodFunctor : Functor { typedef void (TestCase::*Method)(); TestCaseMethodFunctor(TestCase &target, Method method) : target(target), method(method) {} bool operator()() const { target.*method(); return true; } private: TestCase ⌖ Method method; }; } void TestCase::run(TestResult &result) { result.startTest(*this); if (result.protect(TestCaseMethodFunctor(*this, &TestCase::setUp))) { result.protect(TestCaseMethodFunctor(*this, &TestCase::runTest)); } result.protect(TestCaseMethodFunctor(*this, &TestCase::tearDown)); result.endTest(*this); } ...
正例:算法
// cppunit/TestCase.cpp #include "cppunit/core/TestCase.h" #include "cppunit/core/TestResult.h" #include "cppunit/core/Functor.h" namespace { struct TestCaseMethodFunctor : Functor { typedef void (TestCase::*Method)(); TestCaseMethodFunctor(TestCase &target, Method method) : target(target), method(method) {} bool operator()() const { target.*method(); return true; } private: TestCase ⌖ Method method; }; } void TestCase::run(TestResult &result) { result.startTest(*this); if (result.protect(TestCaseMethodFunctor(*this, &TestCase::setUp)) { result.protect(TestCaseMethodFunctor(*this, &TestCase::runTest)); } result.protect(TestCaseMethodFunctor(*this, &TestCase::tearDown)); result.endTest(*this); } ...
override
和private
全部override
的函數(除override
的virtual
析構函數以外)都應該是private
的,以保證按接口編程的良好設計原則。編程
反例:api
// html-parser/filter/AndFilter.h #ifndef EOIPWORPIO_06123124_NMVBNSDHJF_497392 #define EOIPWORPIO_06123124_NMVBNSDHJF_497392 #include "html-parser/filter/NodeFilter.h" #include <list> struct AndFilter : NodeFilter { void add(NodeFilter*); // 設計缺陷:本應該private OVERRIDE(bool accept(const Node&) const); private: std::list<NodeFilter*> filters; }; #endif
正例:數據結構
// html-parser/filter/AndFilter.h #ifndef EOIPWORPIO_06123124_NMVBNSDHJF_497392 #define EOIPWORPIO_06123124_NMVBNSDHJF_497392 #include "html-parser/filter/NodeFilter.h" #include <list> struct AndFilter : NodeFilter { void add(NodeFilter*); private: OVERRIDE(bool accept(const Node&) const); private: std::list<NodeFilter*> filters; }; #endif
inline
inline
頭文件中避免定義inline
函數,除非性能報告指出此函數是性能的關鍵瓶頸。dom
C++
語言將聲明和實現進行分離,程序員爲此不得不在頭文件和實現文件中重複地對函數進行聲明。這是C/C++
天生給咱們的設計帶來的重複。這是一件痛苦的事情,驅使部分程序員直接將函數實現爲inline
。ide
但inline
函數的代碼做爲一種不穩定的內部實現細節,被放置在頭文件裏,其變動所致使的大面積的從新編譯是個大機率事件,爲改善微乎其微的函數調用性能與其相比將得不償失。函數
除非有相關profiling
性能測試報告,代表這部分關鍵的熱點代碼須要被放回頭文件中。性能
但須要注意在特殊的狀況,能夠將實現inline
在頭文件中,由於爲它們建立實現文件過於累贅和麻煩。
virtual
析構函數
空的virtual
函數實現
C++11
的default
函數
inline
對於在編譯單元內部定義的類而言,由於它的客戶數量是肯定的,就是它自己。另外,因爲它原本就定義在源代碼文件中,所以並無增長任何「物理耦合」。因此,對於這樣的類,咱們大能夠將其全部函數都實現爲inline
的,就像寫Java
代碼那樣,Once & Only Once
。
以單態類的一種實現技術爲例,講解編譯時依賴的解耦與匿名命名空間的使用。(首先,應該抵制單態設計的誘惑,單態其本質是面向對象技術中全局變量的替代品。濫用單態模式,猶如濫用全局變量,是一種典型的設計壞味道。只有肯定在系統中惟一存在的概念,才能使用單態模式)。
實現單態,須要對系統中惟一存在的概念進行封裝;但這個概念每每具備巨大的數據結構,若是將其聲明在頭文件中,無疑形成很大的編譯時依賴。
反例:
// ne/NetworkElementRepository.h #ifndef UIJVASDF_8945873_YUQWTYRDF_85643 #define UIJVASDF_8945873_YUQWTYRDF_85643 #include "base/Status.h" #include "base/BaseTypes.h" #include "transport/ne/NetworkElement.h" #include <vector> struct NetworkElementRepository { static NetworkElement& getInstance(); Status add(const U16 id); Status release(const U16 id); Status modify(const U16 id); private: typedef std::vector<NetworkElement> NetworkElements; NetworkElements elements; }; #endif
受文章篇幅的所限,NetworkElement.h
未列出全部代碼實現,但咱們知道NetworkElement
擁有巨大的數據結構,上述設計致使全部包含NetworkElementRepository
的頭文件都被NetworkElement
所間接污染。
此時,其中能夠將依賴置入到實現文件中,解除揭開其嚴重的編譯時依賴。更重要的是,它更好地遵照了按接口編程的原則,改善了軟件的擴展性。
正例:
// ne/NetworkElementRepository.h #ifndef UIJVASDF_8945873_YUQWTYRDF_85643 #define UIJVASDF_8945873_YUQWTYRDF_85643 #include "base/Status.h" #include "base/BaseTypes.h" #include "base/Role.h" DEFINE_ROLE(NetworkElementRepository) { static NetworkElementRepository& getInstance(); ABSTRACT(Status add(const U16 id)); ABSTRACT(Status release(const U16 id)); ABSTRACT(Status modify(const U16 id)); }; #endif
其實現文件包含NetworkElement.h
,將對其的依賴控制在本編譯單元內部。
// ne/NetworkElementRepository.cpp}] #include "transport/ne/NetworkElementRepository.h" #include "transport/ne/NetworkElement.h" #include <vector> namespace { struct NetworkElementRepositoryImpl : NetworkElementRepository { OVERRIDE(Status add(const U16 id)) { // inline implements } OVERRIDE(Status release(const U16 id)) { // inline implements } OVERRIDE(Status modify(const U16 id)) { // inline implements } private: typedef std::vector<NetworkElement> NetworkElements; NetworkElements elements; }; } NetworkElementRepository& NetworkElementRepository::getInstance() { static NetworkElementRepositoryImpl inst; return inst; }
此處,對NetworkElementRepositoryImpl
類的依賴是很是明確的,僅本編譯單元內,全部能夠直接進行inline
,從而簡化了不少實現。
namespace
匿名namespace
的存在經常被人遺忘,但它的確是一個利器。匿名namespace
的存在,使得全部受限於編譯單元內的實體擁有了明確的處所。
自此以後,全部C
風格並侷限於編譯單元內的static
函數和變量;以及相似Java
中常見的private static
的提取函數將經常被匿名namespace
替代。
請記住匿名命名空間也是一種重要的信息隱藏技術。在實現文件中提倡使用匿名namespace
, 以免潛在的命名衝突。
如上例,NetworkElementRepository.cpp
經過匿名namespace
,極大地減低了其頭文件的編譯時依賴。
struct
VS. class
除了名字不一樣以外,class
和struct
惟一的差異是:默承認見性。這體如今定義和繼承時。struct
在定義一個成員,或者繼承時,若是不指明,則默認爲public
,而class
則默認爲private
。
但這些都不是重點,重點在於定義接口和繼承時,冗餘public
修飾符總讓人不舒服。簡單設計四原則告訴告訴咱們,全部冗餘的代碼都應該被剔除。
但不少人會認爲struct
是C
遺留問題,應該避免使用。但這不是問題,咱們不該該否定在寫C++
程序時,依然在使用着不少C
語言遺留的特性。關鍵在於,咱們使用的是C
語言中能給設計帶來好處的特性,何樂而不爲呢?
正例:
// hamcrest/SelfDescribing.h #ifndef OIWER_NMVCHJKSD_TYT_48457_GSDFUIE #define OIWER_NMVCHJKSD_TYT_48457_GSDFUIE struct Description; struct SelfDescribing { virtual void describeTo(Description& description) const = 0; virtual ~SelfDescribing() {} }; #endif
反例:
// hamcrest/SelfDescribing.h #ifndef OIWER_NMVCHJKSD_TYT_48457_GSDFUIE #define OIWER_NMVCHJKSD_TYT_48457_GSDFUIE class Description; class SelfDescribing { public: virtual void describeTo(Description& description) const = 0; virtual ~SelfDescribing() {} }; #endif
更重要的是,咱們確信「抽象」和「信息隱藏」對於軟件的重要性,這促使我將public
接口總置於類的最前面成爲咱們的首選,class
的特性正好與咱們的指望背道而馳(class
的特性正好適合於將數據結構捧爲神物的程序員,它們經常將數據結構置於類聲明的最前面。)
無論你信仰那一個流派,切忌不能混合使用class
和struct
。在大量使用前導聲明的狀況下,一旦一個使用struct
的類改成class
,全部的前置聲明都須要修改。
struct tag
定義C
風格的結構體時,struct tag
完全抑制告終構體前置聲明的可能性,從而阻礙了編譯優化的空間。
反例:
// radio/domain/Cell.h #ifndef AQTYER_023874_NMHSFHKE_7432378293 #define AQTYER_023874_NMHSFHKE_7432378293 typedef struct tag_Cell { WORD16 wCellId; WORD32 dwDlArfcn; } T_Cell; #endif
// radio/domain/Cell.h #ifndef AQTYER_023874_NMHSFHKE_7432378293 #define AQTYER_023874_NMHSFHKE_7432378293 typedef struct { WORD16 wCellId; WORD32 dwDlArfcn; } T_Cell; #endif
爲了兼容C
併爲結構體前置聲明提供便利,以下解法是最合適的。
正例:
// radio/domain/Cell.h #ifndef AQTYER_023874_NMHSFHKE_7432378293 #define AQTYER_023874_NMHSFHKE_7432378293 typedef struct T_Cell { WORD16 wCellId; WORD32 dwDlArfcn; } T_Cell; #endif
須要注意的是,在C
語言中,若是沒有使用typedef
,則定義一個結構體的指針,必須顯式地加上struct
關鍵字:struct T_Cell *pcell
,而C++
沒有這方面的要求。
PIMPL
若是性能不是關鍵問題,考慮使用PIMPL
下降編譯時依賴。
反例:
// mockcpp/ApiHook.h #ifndef OIWTQNVHD_10945_HDFIUE_23975_HFGA #define OIWTQNVHD_10945_HDFIUE_23975_HFGA #include "mockcpp/JmpOnlyApiHook.h" struct ApiHook { ApiHook(const void* api, const void* stub) : stubHook(api, stub) {} private: JmpOnlyApiHook stubHook; }; #endif
正例:
// mockcpp/ApiHook.h #ifndef OIWTQNVHD_10945_HDFIUE_23975_HFGA #define OIWTQNVHD_10945_HDFIUE_23975_HFGA struct ApiHookImpl; struct ApiHook { ApiHook(const void* api, const void* stub); ~ApiHook(); private: ApiHookImpl* This; }; #endif
// mockcpp/ApiHook.cpp #include "mockcpp/ApiHook.h" #include "mockcpp/JmpOnlyApiHook.h" struct ApiHookImpl { ApiHookImpl(const void* api, const void* stub) : stubHook(api, stub) { } JmpOnlyApiHook stubHook; }; ApiHook::ApiHook( const void* api, const void* stub) : This(new ApiHookImpl(api, stub)) { } ApiHook::~ApiHook() { delete This; }
經過ApiHookImpl* This
的橋接,在頭文件中解除了對JmpOnlyApiHook
的依賴,將其依賴控制在本編譯單元內部。
template
當選擇模板時,不得不將其實現定義在頭文件中。當編譯時依賴開銷很是大時,編譯模板將成爲一種負擔。設法下降編譯時依賴,不只僅爲了縮短編譯時間,更重要的是爲了獲得一個低耦合的實現。
反例:
// oss/OssSender.h #ifndef HGGAOO_4611330_NMSDFHW_86794303_HJHASI #define HGGAOO_4611330_NMSDFHW_86794303_HJHASI #include "pub_typedef.h" #include "pub_oss.h" #include "oss_comm.h" #include "pub_commdef.h" #include "base/Assertions.h" #include "base/Status.h" struct OssSender { OssSender(const PID& pid, const U8 commType) : pid(pid), commType(commType) { } template <typename MSG> Status send(const U16 eventId, const MSG& msg) { DCM_ASSERT_TRUE(OSS_SendAsynMsg(eventId, &msg, sizeof(msg), commType,(PID*)&pid) == OSS_SUCCESS); return DCM_SUCCESS; } private: PID pid; U8 commType; }; #endif
爲了實現模板函數send
,將OSS
的一些實現細節暴露到了頭文件中,包含OssSender.h
的全部文件將無心識地產生了對OSS
頭文件的依賴。
提取一個私有的send
函數,並將對OSS
的依賴移入到OssSender.cpp
中,對PID
依賴經過前置聲明解除,最終實現如代碼所示。
正例:
// oss/OssSender.h #ifndef HGGAOO_4611330_NMSDFHW_86794303_HJHASI #define HGGAOO_4611330_NMSDFHW_86794303_HJHASI #include "base/Status.h" #include "base/BaseTypes.h" struct PID; struct OssSender { OssSender(const PID& pid, const U16 commType) : pid(pid), commType(commType) { } template <typename MSG> Status send(const U16 eventId, const MSG& msg) { return send(eventId, (const void*)&msg, sizeof(MSG)); } private: Status send(const U16 eventId, const void* msg, size_t size); private: const PID& pid; U8 commType; }; #endif
識別哪些與泛型相關,哪些與泛型無關的知識,並解開此類編譯時依賴是C++
程序員的必備之技。
模板的編譯時依賴存在兩個基本模型:包含模型,export
模型。export
模型受編譯技術實現的挑戰,最終被C++11
標準放棄。
此時,彷佛咱們只能選擇包含模型。其實,存在一種特殊的場景,適時選擇顯式模板實例化(Explicit Template Instantiated)
,下降模板的編譯時依賴。是能作到下降模板編譯時依賴的。
反例:
// quantity/Quantity.h #ifndef HGGQMVJK_892302_NGFSLEU_796YJ_GF5284 #define HGGQMVJK_892302_NGFSLEU_796YJ_GF5284 #include <quantity/Amount.h> template <typename Unit> struct Quantity { Quantity(const Amount amount, const Unit& unit) : amountInBaseUnit(unit.toAmountInBaseUnit(amount)) {} bool operator==(const Quantity& rhs) const { return amountInBaseUnit == rhs.amountInBaseUnit; } bool operator!=(const Quantity& rhs) const { return !(*this == rhs); } private: const Amount amountInBaseUnit; }; #endif
// quantity/Length.h #ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH #define TYIW7364_JG6389457_BVGD7562_VNW12_JFH #include "quantity/Quantity.h" #include "quantity/LengthUnit.h" typedef Quantity<LengthUnit> Length; #endif
// quantity/Volume.h #ifndef HG764MD_NKGJKDSJLD_RY64930_NVHF977E #define HG764MD_NKGJKDSJLD_RY64930_NVHF977E #include "quantity/Quantity.h" #include "quantity/VolumeUnit.h" typedef Quantity<VolumeUnit> Volume; #endif
如上的設計,泛型類Quantity
的實現都放在了頭文件,不穩定的實現細節,例如計算amountInBaseUnit
的算法變化等因素,將致使包含Length
或Volume
的全部源文件都須要從新編譯。
更重要的是,由於LengthUnit, VolumeUnit
頭文件的包含,若是因需求變化須要增長支持的單位,將間接致使了包含Length
或Volume
的全部源文件也須要從新編譯。
如何控制和隔離Quantity, LengthUnit, VolumeUnit
變化的蔓延,而避免大部分的客戶代碼從新編譯,從而與客戶完全解偶呢?能夠經過顯式模板實例化將模板實現從頭文件中剝離出去,從而避免了沒必要要的依賴。
正例:
// quantity/Quantity.h #ifndef HGGQMVJK_892302_NGFSLEU_796YJ_GF5284 #define HGGQMVJK_892302_NGFSLEU_796YJ_GF5284 #include <quantity/Amount.h> template <typename Unit> struct Quantity { Quantity(const Amount amount, const Unit& unit); bool operator==(const Quantity& rhs) const; bool operator!=(const Quantity& rhs) const; private: const Amount amountInBaseUnit; }; #endif
// quantity/Quantity.tcc #ifndef FKJHJT68302_NVGKS97474_YET122_HEIW8565 #define FKJHJT68302_NVGKS97474_YET122_HEIW8565 #include <quantity/Quantity.h> template <typename Unit> Quantity<Unit>::Quantity(const Amount amount, const Unit& unit) : amountInBaseUnit(unit.toAmountInBaseUnit(amount)) {} template <typename Unit> bool Quantity<Unit>::operator==(const Quantity& rhs) const { return amountInBaseUnit == rhs.amountInBaseUnit; } template <typename Unit> bool Quantity<Unit>::operator!=(const Quantity& rhs) const { return !(*this == rhs); } #endif
// quantity/Length.h #ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH #define TYIW7364_JG6389457_BVGD7562_VNW12_JFH #include "quantity/Quantity.h" struct LengthUnit; struct Length : Quantity<LengthUnit> {}; #endif
// quantity/Length.cpp #include "quantity/Quantity.tcc" #include "quantity/LengthUnit.h" template struct Quantity<LengthUnit>;
// quantity/Volume.h #ifndef HG764MD_NKGJKDSJLD_RY64930_NVHF977E #define HG764MD_NKGJKDSJLD_RY64930_NVHF977E #include "quantity/Quantity.h" struct VolumeUnit; struct Volume : Quantity<VolumeUnit> {}; #endif
// quantity/Volume.cpp #include "quantity/Quantity.tcc" #include "quantity/VolumeUnit.h" template struct Quantity<VolumeUnit>;
Length.h
僅僅對Quantity.h
產生依賴; 特殊地,Length.cpp
沒有產生對Length.h
的依賴,相反對Quantity.tcc
產生了依賴。
另外,Length.h
對LengthUnit
的依賴關係也簡化爲聲明依賴,而對其真正的編譯時依賴,也控制在模板實例化的時刻,即在Length.cpp
內部。
LenghtUnit, VolumeUnit
的變化,及其Quantity.tcc
實現細節的變化,被徹底地控制在Length.cpp, Volume.cpp
內部。
typedef/using
若是使用typedef
,若是存在對Length
的依賴,即便是名字的聲明依賴,除了包含頭文件以外,別無選擇。
另外,若是Quantity
存在virtual
函數時,Length
還有進一步擴展Quantity
的可能性,從而使設計提供了更大的靈活性。
反例:
// quantity/Length.h #ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH #define TYIW7364_JG6389457_BVGD7562_VNW12_JFH #include "quantity/Quantity.h" struct LengthUnit; typedef Quantity<LengthUnit> Length; #endif
正例:
// quantity/Length.h #ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH #define TYIW7364_JG6389457_BVGD7562_VNW12_JFH #include "quantity/Quantity.h" struct LengthUnit; struct Length : Quantity<LengthUnit> {}; #endif