C++箴言:最小化文件之間的編譯依賴

字號:

你進(jìn)入到你的程序中,并對一個類的實(shí)現(xiàn)進(jìn)行了細(xì)微的改變。提醒你一下,不是類的接口,只是實(shí)現(xiàn),僅僅是 private 的東西。然后你重建(rebuild)這個程序,預(yù)計(jì)這個任務(wù)應(yīng)該只花費(fèi)幾秒鐘。畢竟只有一個類被改變。你在 Build 上點(diǎn)擊或者鍵入 make(或者其它等價行為),接著你被驚呆了,繼而被郁悶,就像你突然意識到整個世界都被重新編譯和連接!當(dāng)這樣的事情發(fā)生的時候,你不討厭它嗎?
    問題在于 C++ 沒有做好從實(shí)現(xiàn)中剝離接口的工作。一個類定義不僅指定了一個類的接口而且有相當(dāng)數(shù)量的實(shí)現(xiàn)細(xì)節(jié)。例如:
    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; // implementation detail
    Date theBirthDate; // implementation detail
    Address theAddress; // implementation detail
    };
    在這里,如果不訪問 Person 的實(shí)現(xiàn)使用到的類,也就是 string,Date 和 Address 的定義,類 Person 就無法編譯。這樣的定義一般通過 #include 指令提供,所以在定義 Person 類的文件中,你很可能會找到類似這樣的東西:
    #include
    #include "date.h"
    #include "address.h"
    不幸的是,這樣就建立了定義 Person 的文件和這些頭文件之間的編譯依賴關(guān)系。如果這些頭文件中的一些發(fā)生了變化,或者這些頭文件所依賴的文件發(fā)生了變化,包含 Person 類的文件和使用了 Person 的文件一樣必須重新編譯,這樣的層疊編譯依賴關(guān)系為項(xiàng)目帶來數(shù)不清的麻煩。
    你也許想知道 C++ 為什么堅(jiān)持要將一個類的實(shí)現(xiàn)細(xì)節(jié)放在類定義中。例如,你為什么不能這樣定義 Person,單獨(dú)指定這個類的實(shí)現(xiàn)細(xì)節(jié)呢?
    namespace std {
    class string; // forward declaration (an incorrect
    } // one - see below)
    class Date; // forward declaration
    class Address; // forward declaration
    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;
    ...
    };
    如果這樣可行,只有在類的接口發(fā)生變化時,Person 的客戶才必須重新編譯。
    這個主意有兩個問題。第一個,string 不是一個類,它是一個 typedef (for basic_string)。造成的結(jié)果就是,string 的前向聲明(forward declaration)是不正確的。正確的前向聲明要復(fù)雜得多,因?yàn)樗硗獾哪0?。然而,這還不是要緊的,因?yàn)槟悴粦?yīng)該試著手動聲明標(biāo)準(zhǔn)庫的部件。作為替代,直接使用適當(dāng)?shù)?#includes 并讓它去做。標(biāo)準(zhǔn)頭文件不太可能成為編譯的瓶頸,特別是在你的構(gòu)建環(huán)境允許你利用預(yù)編譯頭文件時。如果解析標(biāo)準(zhǔn)頭文件真的成為一個問題。你也許需要改變你的接口設(shè)計(jì),避免使用導(dǎo)致不受歡迎的 #includes 的標(biāo)準(zhǔn)庫部件。
    第二個(而且更重要的)難點(diǎn)是前向聲明的每一件東西必須讓編譯器在編譯期間知道它的對象的大小??紤]:
    int main()
    {
    int x; // define an int
    Person p( params ); // define a Person
    ...
    }