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