溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

《Effective C++》之條款31:將文件間的編譯依存關(guān)系降至最低

發(fā)布時間:2020-07-29 08:50:22 來源:網(wǎng)絡(luò) 閱讀:642 作者:313119992 欄目:編程語言

《Effective C++》

條款31:將文件間的編譯依存關(guān)系降至最低

       假設(shè)你對C++程序的某個class實現(xiàn)文件做了些輕微修改。注意,修改的不是class接口,而是實現(xiàn),而且只改private成分。然后重新建置這個程序,預(yù)計只花數(shù)秒就好。畢竟只有一個class被修改。當你按下“Build”按鈕或鍵入make指令時,會大吃一驚,然后感到困窘,因為你意識到整個世界都被重新編譯個連接了!那么問題出在哪里呢???

問題出在C++并沒有把“將接口從實現(xiàn)中分離”這事做的很好。Class的定義式不只詳細描述了class接口,還包括十足的實現(xiàn)細目。例如:

#include <string>
#include "date.h"
#include "address.h"
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;//實現(xiàn)細目
    Date theBirthDate;//實現(xiàn)細目
    Address theAddress;//實現(xiàn)細目
};

其中:

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

       由于這些頭文件,使得Person定義文件和其含入文件之間形成了一種編譯依存關(guān)系。如果這些頭文件中有任何一個被改變,或這些頭文件所依賴的其他頭文件有任何改變,那么每一個含入Person class的文件就得重新編譯,任何使用Person class的文件也必須重新編譯。這樣的連串編譯依存關(guān)系會對許多項目造成難以形容的災(zāi)難。

如下是一個解決方案的思路:

將對象實現(xiàn)細目隱藏于一個指針背后。把Person分割成兩個classes,一個只提供接口,另一個負責實現(xiàn)該接口。如果負責實現(xiàn)的那個所謂implementation class 取名為PersonImpl,Person將定義如下:

#include <string>
#include <memory>

class PersonImpl;//Person實現(xiàn)類的前置申明
class Date;//Person接口用到的classes(Date,Address)的前置申明
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::tr1::shared_ptr<PersonImpl> pImpl;//智能指針shared_ptr,指向?qū)嵨?};

這個分離的關(guān)鍵在于以“申明的依存性”替換“定義的依存性”,那正是編譯依存性最小化的本質(zhì):現(xiàn)實中讓頭文件盡可能自我滿足,萬一做不到,則讓它與其他文件內(nèi)的申明式相依。其他每一件事都源自于這個簡單的設(shè)計策略:

1.如果使用object reference或object pointers可以完成任務(wù),就不要使用objects。

2.如果能夠,盡量以class申明式替換class定義式。

3.為申明式和定義式提供不同的頭文件。


Handle classes其中具體做法1是將他們的所有函數(shù)轉(zhuǎn)交給相應(yīng)的實現(xiàn)類并由后者完成實際工作。例如下面是Person兩個成員函數(shù)的實現(xiàn):

#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構(gòu)造函數(shù)以new調(diào)用PersonImpl構(gòu)造函數(shù),以及Person::name函數(shù)內(nèi)調(diào)用PersonImpl::name。這是重要的,讓Person變成一個Handle class并不會改變它做的事,只會改變它做事的方法。

另一個制作Handle class的辦法是,令Person成為一種特殊的abstract base class,稱為interface class。這種class的目的是詳細一一描述derived classes的接口,因此它通常不帶成員變量,也沒有構(gòu)造函數(shù),只有一個virtual析構(gòu)函數(shù)以及一組pure virtual函數(shù),用來描述整個接口。


總結(jié):

  1. 支持“編譯依存性最小化”的一般構(gòu)想是:相依于申明式,不要相依于定義式?;诖藰?gòu)想的兩個手段是Handle classes和Interface classes。

  2. 程序庫頭文件應(yīng)該以“完全且僅有申明式”的形式存在。這種做法不論是否設(shè)計template都適用。

2016-11-08 23:10:40

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI