在說這一條款之前,先要了解一下C/C++的編譯知識,假設有三個類ComplexClass, SimpleClass1和SimpleClass2,采用頭文件將類的聲明與類的實現分開,這樣共對應於6個文件,分別是ComplexClass.h,ComplexClass.cpp,SimpleClass1.h,SimpleClass1.cpp,SimpleClass2.h,SimpleClass2.cpp。
ComplexClass復合兩個BaseClass,SimpleClass1與SimpleClass2之間是獨立的,ComplexClass的.h是這樣寫的:
#ifndef COMPLESS_CLASS_H #define COMPLESS_CLASS_H #include “SimpleClass1.h” #include “SimpleClass2.h” class ComplexClass { SimpleClass1 xx; SimpleClass2 xxx; }; … #endif /* COMPLESS _CLASS_H */
我們來考慮以下幾種情況:
Case 1:
現在SimpleClass1.h發生了變化,比如添加了一個新的成員變量,那么沒有疑問,SimpleClass1.cpp要重編,SimpleClass2因為與SimpleClass1是獨立的,所以SimpleClass2是不需要重編的。
那么現在的問題是,ComplexClass需要重編嗎?
答案是“是”,因為ComplexClass的頭文件里面包含了SimpleClass1.h(使用了SimpleClass1作為成員對象的類),而且所有使用ComplexClass類的對象的文件,都需要重新編譯!
如果把ComplexClass里面的#include “SimpleClass1.h”給去掉,當然就不會重編ComplexClass了,但問題是也不能通過編譯了,因為ComplexClass里面聲明了SimpleClass1的對象xx。那如果把#include “SimpleClass1.h”換成類的聲明class SimpleClass1,會怎么樣呢?能通過編譯嗎?
答案是“否”,因為編譯器需要知道ComplexClass成員變量SimpleClass1對象的大小,而這些信息僅由class SimpleClass1是不夠的,但如果SimpleClass1作為一個函數的形參,或者是函數返回值,用class SimpleClass1聲明就夠了。如:
1 // ComplexClass.h 2 class SimpleClass1; 3 … 4 SimpleClass1 GetSimpleClass1() const; 5 …
但如果換成指針呢?像這樣:
1 // ComplexClass.h 2 #include “SimpleClass2.h” 3 4 class SimpleClass1; 5 6 class ComplexClass: 7 { 8 SimpleClass1* xx; 9 SimpleClass2 xxx; 10 };
這樣能通過編譯嗎?
答案是“是”,因為編譯器視所有指針為一個字長(在32位機器上是4字節),因此class SimpleClass1的聲明是夠用了。但如果要想使用SimpleClass1的方法,還是要包含SimpleClass1.h,但那是ComplexClass.cpp做的,因為ComplexClass.h只負責類變量和方法的聲明。
那么還有一個問題,如果使用SimpleClass1*代替SimpleClass1后,SimpleClass1.h變了,ComplexClass需要重編嗎?
先看Case2。
Case 2:
回到最初的假定上(成員變量不是指針),現在SimpleClass1.cpp發生了變化,比如改變了一個成員函數的實現邏輯(換了一種排序算法等),但SimpleClass1.h沒有變,那么SimpleClass1一定會重編,SimpleClass2因為獨立性不需要重編,那么現在的問題是,ComplexClass需要重編嗎?
答案是“否”,因為編譯器重編的條件是發現一個變量的類型或者大小跟之前的不一樣了,但現在SimpleClass1的接口並沒有任務變化,只是改變了實現的細節,所以編譯器不會重編。
Case 3:
結合Case1和Case2,現在我們來看看下面的做法:
1 // ComplexClass.h 2 #include “SimpleClass2.h” 3 4 class SimpleClass1; 5 6 class ComplexClass 7 { 8 SimpleClass1* xx; 9 SimpleClass2 xxx; 10 };
1 // ComplexClass.cpp 2 3 void ComplexClass::Fun() 4 { 5 SimpleClass1->FunMethod(); 6 }
請問上面的ComplexClass.cpp能通過編譯嗎?
答案是“否”,因為這里用到了SimpleClass1的具體的方法,所以需要包含SimpleClass1的頭文件,但這個包含的行為已經從ComplexClass里面拿掉了(換成了class SimpleClass1),所以不能通過編譯。
如果解決這個問題呢?其實很簡單,只要在ComplexClass.cpp里面加上#include “SimpleClass1.h”就可以了。換言之,我們其實做的就是將ComplexClass.h的#include “SimpleClass1.h”移至了ComplexClass1.cpp里面,而在原位置放置class SimpleClass1。
這樣做是為了什么?假設這時候SimpleClass1.h發生了變化,會有怎樣的結果呢?
SimpleClass1自身一定會重編,SimpleClass2當然還是不用重編的,ComplexClass.cpp因為包含了SimpleClass1.h,所以需要重編,但換來的好處就是所有用到ComplexClass的其他地方,它們所在的文件不用重編了!因為ComplexClass的頭文件沒有變化,接口沒有改變!
總結一下,對於C++類而言,如果它的頭文件變了,那么所有這個類的對象所在的文件都要重編,但如果它的實現文件(cpp文件)變了,而頭文件沒有變(對外的接口不變),那么所有這個類的對象所在的文件都不會因之而重編。
因此,避免大量依賴性編譯的解決方案就是:在頭文件中用class聲明外來類,用指針或引用代替變量的聲明;在cpp文件中包含外來類的頭文件。