您好,登錄后才能下訂單哦!
《Effective C++》
條款31:將文件間的編譯依存關(guān)系降至最低
假設(shè)你對C++程序的某個class實現(xiàn)文件做了些輕微修改。注意,修改的不是class接口,而是實現(xiàn),而且只改private成分。然后重新建置這個程序,預(yù)計只花數(shù)秒就好。畢竟只有一個class被修改。當你按下“Build”按鈕或鍵入make指令時,會大吃一驚,然后感到困窘,因為你意識到整個世界都被重新編譯個連接了!那么問題出在哪里呢???
問題出在C++并沒有把“將接口從實現(xiàn)中分離”這事做的很好。Class的定義式不只詳細描述了class接口,還包括十足的實現(xiàn)細目。例如:
#include <string> #include "date.h" #include "address.h" class Person { public: Person(const std::string& name,const Date& birthday, const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ... private: std::string theName;//實現(xiàn)細目 Date theBirthDate;//實現(xiàn)細目 Address theAddress;//實現(xiàn)細目 };
其中:
#include <string> #include "date.h" #include "address.h"
由于這些頭文件,使得Person定義文件和其含入文件之間形成了一種編譯依存關(guān)系。如果這些頭文件中有任何一個被改變,或這些頭文件所依賴的其他頭文件有任何改變,那么每一個含入Person class的文件就得重新編譯,任何使用Person class的文件也必須重新編譯。這樣的連串編譯依存關(guān)系會對許多項目造成難以形容的災(zāi)難。
如下是一個解決方案的思路:
將對象實現(xiàn)細目隱藏于一個指針背后。把Person分割成兩個classes,一個只提供接口,另一個負責實現(xiàn)該接口。如果負責實現(xiàn)的那個所謂implementation class 取名為PersonImpl,Person將定義如下:
#include <string> #include <memory> class PersonImpl;//Person實現(xiàn)類的前置申明 class Date;//Person接口用到的classes(Date,Address)的前置申明 class Address; class Person { public: Person(const std::string& name,const Date& birthday, const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ... private: std::tr1::shared_ptr<PersonImpl> pImpl;//智能指針shared_ptr,指向?qū)嵨?};
這個分離的關(guān)鍵在于以“申明的依存性”替換“定義的依存性”,那正是編譯依存性最小化的本質(zhì):現(xiàn)實中讓頭文件盡可能自我滿足,萬一做不到,則讓它與其他文件內(nèi)的申明式相依。其他每一件事都源自于這個簡單的設(shè)計策略:
1.如果使用object reference或object pointers可以完成任務(wù),就不要使用objects。
2.如果能夠,盡量以class申明式替換class定義式。
3.為申明式和定義式提供不同的頭文件。
Handle classes其中具體做法1是將他們的所有函數(shù)轉(zhuǎn)交給相應(yīng)的實現(xiàn)類并由后者完成實際工作。例如下面是Person兩個成員函數(shù)的實現(xiàn):
#include "Person.h" #include "PersonImpl.h" Person::Person(const std::string& name,const Date& birthday, const Address& addr) : pImpl(new PersonImpl(name,birthday,addr)) { } std::string Person::name() const { return pImpl->name(); }
請注意,Person構(gòu)造函數(shù)以new調(diào)用PersonImpl構(gòu)造函數(shù),以及Person::name函數(shù)內(nèi)調(diào)用PersonImpl::name。這是重要的,讓Person變成一個Handle class并不會改變它做的事,只會改變它做事的方法。
另一個制作Handle class的辦法是,令Person成為一種特殊的abstract base class,稱為interface class。這種class的目的是詳細一一描述derived classes的接口,因此它通常不帶成員變量,也沒有構(gòu)造函數(shù),只有一個virtual析構(gòu)函數(shù)以及一組pure virtual函數(shù),用來描述整個接口。
總結(jié):
支持“編譯依存性最小化”的一般構(gòu)想是:相依于申明式,不要相依于定義式?;诖藰?gòu)想的兩個手段是Handle classes和Interface classes。
程序庫頭文件應(yīng)該以“完全且僅有申明式”的形式存在。這種做法不論是否設(shè)計template都適用。
2016-11-08 23:10:40
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。