1.編譯依存關係
某些時候只修改了某個類的private,結果卻有一大堆檔案需要編譯。問題出在c++並沒有把“將介面從實現中分離”這事做好。Class的定義式不只詳細敘述了class介面,還包括十足的實現細目。例如:
class Perpson {public: std::string name() const; std::string birthData() const; std::string address() const;...private: std::string theName; //實現細目 Date theBirthDate; Address theAddress;};
在Person定義檔案的最上方很可能存在這樣的東西:
#include <string>#include "date.h"#include "address.h"
不幸的是,這麼一來便是在Person定義檔案和其含入檔案之間形成了一種編譯儲存關係。如果這些標頭檔中有任何一個被改變,或者這些檔案檔案所做事的其他標頭檔有任何改變,那麼每一個含入Person class的檔案就得重新編譯。這樣的連串編譯依存關係會對許多項目造成難以形容的災難。
考慮加上前置聲明,以去掉#include包含的標頭檔:
class Date;class Address;
這樣做有一個困難,編譯器必須在編譯期間知道對象的大小。考慮這個:
int main(){ int x; Person p; ...}
當編譯器看到x的定義式,它知道必須分配多少記憶體才夠持有一個int。沒問題,每個編譯器都知道一個int有多大。當編譯器看到p的定義式,它也知道必須分配空間以旋轉一個Person,但它如何知道一個Person對象有多大呢?唯一的辦法就是詢問class定義式。然而如果class定義式可以合法地不列出實現細目(使用前置聲明),編譯器如何知道該分配多少空間?
2.Handle class
我們可以使用pimpl,“將對象實現細目隱藏於一個指標背後”的遊戲。針對Person我們可以這樣做:把Person分割為兩個class,一個只提供介面,另一個負責實現該介面。負責實現的那個類取名為PersonImpl,Person定義如下:
#include <string>#include <memory>class PersonImpl;class Date;class Address;class Person {public: std::string name() const; ...private: std::tr1::shared_ptr<PersonImpl> pImpl; //指標,};
上面的這般設計常被稱為pimpl idiom(pimpl 是“pointer to implementation”),像Person這樣使用pimpl idiom的class,往往被稱為Handle class。這樣的設定計之下,Person的客戶就完全與Dates等實現細目分離,真正實現了“介面與實現分離”。這個分離的關鍵在於以“聲明的依存性”替換“定義依存性”,其實每一件事都源自這幾個簡單的設計策略:
1)如果使用object references 或object pointers可以完成任務,就不要使用objects。
2)如果能夠,盡量以class聲明式替換class定義式。注意,當你聲明一個函數而它用到某個class時,你並不需要該class的定義;縱使以by value方式傳遞該類型的參數(或傳回值)亦然:
class Date; //class 聲明式Date today(); //沒問題--這裡並不需要Date的定義式void clearAppointments(Date d);
3)為聲明和定義提供不同的標頭檔
3.Interface class
令Person成為一種特殊的abstract base class,稱為Interface class。如下:
class Person {public: static std::tr1::shared_ptr<Person> create(); //使用一個Factory 方法建立真實對像 virtual ~Person(); virtual std::string name() const = 0; virtual std::string birthDate() const = 0; virtual std::string address() const = 0; }
class RealPerson: public Person {...}static std::tr1::shared_ptr<Person> create { return std::tr1::shared_prt<Person>(new RealPerson());}
注,將Factory 方法提出來,放到單獨的工廠類中,效果應該更好。
摘自Effective c++
PS:關於pimpl的實現方式
pimpl可以以下面的方式實現:
//Person.h#include <memory>class Person{public:Person(void);~Person(void);private:class PersonImpl;std::tr1::shared_ptr<PersonImpl> pImpl;};//Person.cpp#include "Person.h"class Person::PersonImpl {};Person::Person(void){pImpl = std::tr1::shared_ptr<PersonImpl>(new PersonImpl);}Person::~Person(void){}
PersonImpl定義在Person域中,外部不再可見