C++之將檔案間的編譯依存關係降至最低(31)---《Effective C++》__C++

來源:互聯網
上載者:User
條款31:將檔案間的編譯依存關係降至最低

問題提出:如果我們將某個class的實現檔案做了某些修改,修改並不是class的介面,而是其實現部分,而且只改了其private部分,然後重建整個檔案,然鵝當你進行編譯的時候,可以發現該類都被重新編譯和連結了,什麼鬼。。。

問題的原因是C++並沒有“將介面從實現中分離”該部分做的很好,class的形式不僅包括介面,同時還包括十足的實現項目,如:

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;//實現細目    Date theBirthDate;//實現細目    Address theAddress;//實現細目};  

這裡的class Person無法通過編譯,因為這個檔案中包含了類Date,address和string的定義是,這樣的定義式通常通過#include指示符提供。Person的上面通常包括如下代碼:

#include <string>#include "date.h"#include "address.h"

這樣Person定義檔案和匯入檔案之間形成了一種“編譯依存關係”,如果標頭檔中有任何一個被改變,或者標頭檔中依賴的其他標頭檔有任何改變,那麼每一個含入的Person class的檔案就需要重新編譯,任何使用Person class的檔案同樣也需要被重新編譯,這樣連串編譯依存關係將使得整個項目超級複雜,同時容易崩潰。

既然這樣問題很大的話,為什麼C++堅持將class的實現細目至於class的定義式中呢。為什麼不這樣定義Person,將實現細目分開敘述。。。代碼如下:

namespace std{    class string;}class Date;class Address;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;    ...}; 

這樣實現的話將存在兩個問題:
1)string並不是class,只是basic_string,因此針對上述string而做的前置聲明並不明確,正確的前置聲明比較複雜,因為設計額外的templates,然而這並不重要;
2)編譯器需要在編譯期間內知道對象的大小。
看看C++中是如何解決這個問題的吧。

#include <string>#include <memory>//此乃為了trl::shared_ptr而含入;class PersonImpl;class Date;class Address;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::trl::shared_ptr<PersonImpl> pImpl;//指標,指向實現物

可以看到這裡main class中只包含一個指標成員,指向其實作類別,這般設計通常被稱為pimpl idiom,這種class內的指標往往就是pImpl,就像上面代碼這樣。這種設計,Person的客戶就完全與Date,Address以及Person的實現細目完全分離了。
** 這個分離的關鍵在於“聲明的依存性”替換為“定義的依存性”,那正是編譯器依存性最小化的本質,

**現實中讓標頭檔儘可能自我滿足,萬一做不到,則讓它與其他檔案內的聲明式(而非定義式)相依,其他每一件事都源自於這個簡單的設計策略:
1)如果使用object references或者object pointers可以完成任務,就不要使用object,你可以只靠一個型別宣告式就可以定義出指向該類型的references和pointers,但如果定義某類型的objects,就需要用到該類型的定義式;
2)如果能夠,盡量用class聲明式替換為class定義式,注意,當你聲明一個函數而它用到某個class時候,你並不需要class的定義;
3)為聲明式和定義式提供不同的標頭檔。

像Person這樣使用pimpl idiom的class,往往被稱為Handle classes,那麼如何使用實現Person中的成員函數呢。答案有兩種:

1)當然是利用PersonImpl實作類別中的實現函數啦。

#include "Person.h"#include "PersonImpl.h"Person::Person(const std::string& name,const Date& birthday,const Address& addr):pImpl(new PersonImpl(name,birthday,addr)){}...std::string Person::name() const{    return pImpl->name();}

這裡需要注意Person建構函式伊new調用PersonImpl的建構函式,已經Person::name函數中調用PersonImpl::name,讓Person變成一個Handle class並不會改變它做的事,只會改變它做事的方法。
2)令Person稱為一種特殊的abstract base class(抽象基類),稱為Interface class,這種class的目的是詳細一一描述derived classes的介面,因此它通常不帶成員變數,也沒有建構函式,只有一系列virtual函數和一個virtual解構函式,通常我們可以設計一個factory工廠函數或者virtual建構函式進行實現,如:

class Person{public:    virtual ~Person();    virtual std::string name()const=0;    virtual std::string birthDate() const=0;    virtual std::string address() cosnt=0;    ...};class RealPerson:public Person{public:     RealPerson(const std::string& name,const Date& birthday,const Address& addr):theName(name),theBirthDate(birthday),theAddress(addr)    {}    virtual ~RealPerson(){}    std::string name() const;    std::string birthDate() const;    std::string address() const;private:    std::string theName;    Date theBirthDate;    Address theAddress;};class Person{public:    ...    static std::trl::shared_ptr<Person> create(const std::string& name,const Date& birthday,const Address& addr);    ...};

對於Handle classes來說,成員函數必須通過implementation pointer取得對象那個資料,那回味每一次訪問添加一層間接性。每一個對象小號的記憶體數量必須增加implementation pointer大小,最後impementation pointer必須初始化,指向一個動態分配得來的implementation object,所以你需要體會因動態記憶體分配(及其後的釋放動作)而來的而外開銷,以及遭遇bad_alloc有慈航的可能性;
對於Interface classes來說,每個函數都是virtual,所以你必須為每次函數調用付出一個間接跳躍成本,同時Interface class派生出來的向必須內含一個vptr,這個只恨可能會增加存放對象所需要的記憶體數量。

總結:
1)支援“編譯依存性最小化”的一般方法是:相依賴於聲明書式,不要依賴於定義式,基於次構想的兩個手段是Handle classes和Interface classes。
2)程式庫標頭檔應該以“完全且僅有聲明式”的形式存在,這種做法不論是否設計templates都適用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.