C++文件依存關系


如果現在你做的C++項目(課題)包含的文件沒有超過1000個,你可以選擇略過此文,不過建議繼續瀏覽。
如果你覺得重新編譯文件的時間很短或者時間長一點無所謂,反正需要重新編譯,那么你也可以選擇略過此文,不過也建議瀏覽。
如果你想學習或者關心這塊內容,那么此文必定會給你帶來收獲。

首先我不給出依存關系的定義,我給出一個例子。

 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 };
這樣只有People該接口被改變時才會重新編譯,但是這樣有連個問題,第一點string不是class,它是個typedef basic_string<char> string。因此上述前置聲明不正確(附其在stl完全代碼);,正確的前置聲明比較復雜。其實對於標准庫部分,我們僅僅通過#include預處理命令包括進來就可以了。
 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
這樣,People就完全與Date、Imge以及People的實現分離了上面那些類任何修改都不需要重新編譯People文件了。另外這樣寫加強了封裝。這樣也就降低了文件的依存關系。
這里總結下降低依存性方法:
1.如果可以類聲明就不要使用類定義了。
2.將數據通過一個指向該數據的指針表示。
3.為聲明式和定義式提供不同的頭文件。
  這兩個文件必須保持一致性,如果有個聲明式被改變了,兩個文件都得改變。因此一般會有一個#include一個聲明文件而不是前置聲明若干函數。
  像People這樣定
 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類解除了接口和實現之間的耦合關系,從而降低了文件間的編譯依存性。但同時也損耗了一些性能與空間。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM