首先我不給出依存關系的定義,我給出一個例子。
1 class Peopel{ 2 public: 3 People(const std::string & name,const Date& brithday,Image Img) 4 std::string name( ) const; 5 Date birthDate( ) const; 6 Image img( ) const; 7 ... 8 private: 9 std::string theName; //名字 10 Date theBirthDate; //生日 11 Image img; //圖片 12 };
如果編譯器沒有知道類string,Date和Image的定義,class People是無法通過編譯的。一般該定義式是由#include包含的頭文件所提供的,所以一般People上面有這些預處理命令
1 #include <string> 2 #include "date.h" 3 #inblude "image.h" 4 class Peopel{ 5 public: 6 People(const std::string & name,const Date& brithday,Image Img) 7 std::string name( ) const; 8 Date birthDate( ) const; 9 Image img( ) const; 10 ... 11 private: 12 std::string theName; //名字 13 Date theBirthDate; //生日 14 Image img; //圖片 15 };
那么這樣People定義文件與該三個文件之間就形成了一種編譯依存關系。如果這些頭文件任何一個文件被改變,或這些頭文件所依賴其他頭文件任何改變,那么每一個包含People類的文件就需要重新編譯,使用People類文件也需要重新編譯。想想如果一個項目包含一個上千的文件,每個文件包含其他幾個文件,依次這樣下來,改動一個文件內容,那么就需要幾乎重新編譯整個項目了,這可以說很槽糕了。
我們可以進行如下改動
1 namespace std { 2 class string; 3 } 4 class Date; 5 class Image; 6 7 class Peopel{ 8 public: 9 People(const std::string & name,const Date& brithday,Image& Img) 10 std::string name( ) const; 11 Date birthDate( ) const; 12 Image img( ) const; 13 ... 14 private: 15 std::string theName; //名字 16 Date theBirthDate; //生日 17 Image img; //圖片 18 };
1 #ifndef __STRING__ 2 #define __STRING__ 3 4 #include <std/bastring.h> 5 6 extern "C++" { 7 typedef basic_string <char> string; 8 // typedef basic_string <wchar_t> wstring; 9 } // extern "C++" 10 11 #endif
1 int main(int argv,char * argc[ ]) 2 { 3 int x; 4 People p( 參數 ); 5 ... 6 }
當編譯器看到x的定義式,它知道必須分配多少內存,但是看到p定義式就無法知道了。但是如果設置為指針的話,就清楚了,因為指針本身大小編譯器是知道的。
#include <string> #include <memory> class PeopleImpl; class Date; class Image; class People{ public: People(const std::string & name, const Date& brithday, const Image &Img); std::string name( ) const; Date birthDate( ) const; Imge img( ) const; ... private: PeopleImpl * pImpl; }
PeopleImpl包含下面這三個數據,而People的成員變量指針指向這個PeopleImpl,那么現在編譯器通過People定義就知道了其分配空間的大小了,一個指針的大小。
1 public PeopleImpl 2 { 3 public: 4 PeopleImple(...) 5 ... 6 private: 7 std::string theName; //名字 8 Date theBirthDate; //生日 9 Image img; //圖片 10 }
1 #include "People.h" 2 #include "PeopleImpl.h" 3 4 People::People(const std::string& name, const Date& brithday, const Image& Img) 5 :pImpl(new PersonImpl(name,brithday,addr)) 6 { } 7 std::string People::name( ) const 8 { 9 return pImpl->name( ); 10 }
而另外一種Handle類寫法是令People成為一種特殊的abstract base class稱為Interface類。看到interface這個關鍵字或許熟悉C#、java的同學可能已經恍然大悟了。這種接口它不帶成員變量,也沒有構造函數,只有一個virtual析構函數,以及一組純虛函數,用來表示整個接口。針對People而寫的interface class看起來是這樣的。
1 class People{ 2 public: 3 virtual ~People( ); 4 virtual std::string name( ) const = 0; 5 virtual Date brithDate( ) const =0; 6 virtual Image address( ) const =0; 7 ... 8 };
怎么創建對象呢?它們通常調用一個特殊函數。這樣的函數通常稱為工廠函數或者虛構造函數。它們返回指針指向動態分配所得對象,而該對象支持interface類的接口。
1 class People { 2 public: 3 ... 4 static People* create(const std::string& name,const Date& brithday, const Image& Img); 5 };
支持interface類接口的那個類必須定義出來,而且真正的構造函數必須被調用
1 class RealPeople:public People{ 2 public: 3 RealPeople(const std::string& name,const Date& birthday,const Image& Img) 4 :theName(name),theBrithDate(brithday),theImg(Img) 5 {} 6 virtual ~RealPeople() { } 7 std::string name( ) const; 8 Date birthDate( ) const; 9 Image img( ) const; 10 private: 11 std::string theName; 12 Date theBirthDate; 13 Image theImg; 14 }
有了RealPeople類,我們People::create可以這樣寫
1 People* People::create(const std::string& name, const Date& birthday, const Image& Img) 2 { 3 return static_cast<People *>(new RealPerson(name,birthday,Img)); 4 }
Handle類與interface類解除了接口和實現之間的耦合關系,從而降低了文件間的編譯依存性。但同時也損耗了一些性能與空間。