• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Shuffy

            不斷的學(xué)習(xí),不斷的思考,才能不斷的進(jìn)步.Let's do better together!
            posts - 102, comments - 43, trackbacks - 0, articles - 19
            【轉(zhuǎn)】http://www.shnenglu.com/tiandejian/archive/2008/01/01/ec_31.html

            第31條:     要努力減少文件間的編譯依賴(lài)

            為了更新某個(gè)類(lèi)的某個(gè)功能實(shí)現(xiàn),你可能需要在浩瀚 C++ 的代碼中做出一個(gè)細(xì)小的修改,要提醒你的是,修改的地方不是類(lèi)接口,而是實(shí)現(xiàn)本身,并且僅僅是私有成員。完成修改之后,你需要對(duì)程序進(jìn)行重新構(gòu)建,這時(shí)你肯定會(huì)認(rèn)為這一過(guò)程將十分短暫,畢竟你只對(duì)一個(gè)類(lèi)做出了修改。當(dāng)你按下“構(gòu)建”按鈕,或輸入 make 命令(或者其他什么等價(jià)的操作)之后,你驚呆了,然后你就會(huì)陷入困惑中,因?yàn)槟惆l(fā)現(xiàn)一切代碼都重新編譯并重新鏈接了!所發(fā)生的事情難道不會(huì)讓你感到不快嗎?

            問(wèn)題的癥結(jié)在于: C++ 并不擅長(zhǎng)區(qū)分接口和實(shí)現(xiàn)。一個(gè)類(lèi)的定義不僅指定了類(lèi)接口的內(nèi)容,而且指明了相當(dāng)數(shù)量的實(shí)現(xiàn)細(xì)節(jié)。請(qǐng)看下面的示例:

            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;        // 具體實(shí)現(xiàn)

                  Date theBirthDate;          // 具體實(shí)現(xiàn)

                  Address theAddress;         // 具體實(shí)現(xiàn)

            };

            這里,如果無(wú)法訪(fǎng)問(wèn) Person 具體實(shí)現(xiàn)所使用的類(lèi)(也就是 string 、 Date Address )定義,那么 Person 類(lèi)將不能夠得到編譯。通常這些定義通過(guò) #include 指令來(lái)提供,因此在定義 Person 類(lèi)的文件中,你應(yīng)該能夠找到這樣的內(nèi)容:

            #include <string>

            #include "date.h"

            #include "address.h"

            不幸的是,這樣做使得定義 Person 的文件對(duì)這些頭文件產(chǎn)生了依賴(lài)。如果任一個(gè)頭文件的內(nèi)容被修改了,或者這些頭文件所依賴(lài)的另外某個(gè)頭文件被修改,那么包含 Person 類(lèi)的文件就必須重新編譯,有多少個(gè)文件包含 Person ,就要進(jìn)行多少次編譯操作。這種瀑布式的編譯依賴(lài)將招致無(wú)法估量的災(zāi)難式的后果。

            你可能會(huì)考慮:為什么 C++ 堅(jiān)持要將類(lèi)具體實(shí)現(xiàn)的細(xì)節(jié)放在類(lèi)定義中呢?假如說(shuō),如果我們換一種方式定義 Person ,單獨(dú)編寫(xiě)類(lèi)的具體實(shí)現(xiàn),結(jié)果又會(huì)怎樣呢?

            namespace std {

                 class string;               // 前置聲明 ( 這個(gè)是非法的,參見(jiàn)下文 )

            }

             

            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;

                ...

            };

            如果這樣可行,那么對(duì)于 Person 的客戶(hù)端程序員來(lái)說(shuō),僅在類(lèi)接口有改動(dòng)時(shí),才需要進(jìn)行重新編譯。

            這種想法存在著兩個(gè)問(wèn)題。首先, string 不是一個(gè)類(lèi),它是一個(gè) typedef typedef basic_string<char> string )。于是,針對(duì) string 的前置聲明就是非法的。實(shí)際上恰當(dāng)?shù)那爸寐暶饕獜?fù)雜的多,因?yàn)樗婕暗狡渌哪0濉H欢@不是主要問(wèn)題,因?yàn)槟惚緛?lái)就不應(yīng)該嘗試手工聲明標(biāo)準(zhǔn)庫(kù)的內(nèi)容。僅僅使用恰當(dāng)?shù)?/span> #include 指令就可以了。標(biāo)準(zhǔn)頭文件一般都不會(huì)成為編譯中的瓶頸,尤其是在你的編譯環(huán)境允許你利用事先編譯好的頭文件時(shí)更為突出。如果分析標(biāo)準(zhǔn)頭文件對(duì)你來(lái)說(shuō)的確是件麻煩事,那么你可能就需要改變你的接口設(shè)計(jì),避免去使用那些會(huì)帶來(lái)多余 #include 指令的標(biāo)準(zhǔn)類(lèi)成員。

            對(duì)所有的類(lèi)做前置聲明會(huì)遇到的第二個(gè)(同時(shí)也是更顯著的)難題是:在編譯過(guò)程中,編譯器需要知道對(duì)象的大小。請(qǐng)觀察下面的代碼:

            int main()

            {

              int x;                          // 定義一個(gè) int

             

              Person p( params );             // 定義一個(gè) Person

               ...

            }

            當(dāng)編譯器看到了 x 的定義時(shí),它們就知道該為其分配足夠的內(nèi)存空間(通常位于棧中)以保存一個(gè) int 值。這里沒(méi)有問(wèn)題。每一種編譯器都知道 int 的大小。當(dāng)編譯器看到 p 的定義時(shí),他們知道該為其分配足夠的空間以容納一個(gè) Person ,但是他們又如何得知 Person 對(duì)象的大小呢?得到這一信息的唯一途徑就是通過(guò)類(lèi)定義,但是如果允許類(lèi)定義省略具體實(shí)現(xiàn)的細(xì)節(jié),那么編譯器又如何得知需要分配多大空間呢?

            同樣的問(wèn)題不會(huì)在 Smalltalk Java 中出現(xiàn),因?yàn)樵谶@些語(yǔ)言中,每當(dāng)定義一個(gè)對(duì)象時(shí),編譯器僅僅分配指向該對(duì)象指針大小的空間。也就是說(shuō),在這些語(yǔ)言中,上面的代碼將做如下的處理:

            int main()

            {

             int x;                         // 定義一個(gè) int

             

             Person *p;                     // 定義一個(gè) Person

             ...

            }

            當(dāng)然,這段代碼在 C++ 中是合法的,于是你可以自己通過(guò)“將對(duì)象實(shí)現(xiàn)隱藏在指針之后”來(lái)玩轉(zhuǎn)前置聲明。對(duì)于 Person 而言,實(shí)現(xiàn)方法之一就是將其分別放在兩個(gè)類(lèi)中,一個(gè)只提供接口,另一個(gè)存放接口對(duì)應(yīng)的具體實(shí)現(xiàn)。暫且將具體實(shí)現(xiàn)類(lèi)命名為 PersonImpl Person 類(lèi)的定義應(yīng)該是這樣的:

            #include <string>                // 標(biāo)準(zhǔn)庫(kù)成員,不允許對(duì)其進(jìn)行前置聲明

             

            #include <memory>               // 為使用 tr1::shared_ptr; 稍后介紹

             

            class PersonImpl;               // Person 實(shí)現(xiàn)類(lèi)的前置聲明

             

            class Date;                     // Person 接口中使用的類(lèi)的前置聲明

            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:                        // 指向?qū)崿F(xiàn)的指針

             std::tr1::shared_ptr<PersonImpl> pImpl;

            };                // 關(guān)于 std::tr1::shared_ptr 的更多信息,參見(jiàn) 13

            在這里,主要的類(lèi)( Person )僅僅包括一個(gè)數(shù)據(jù)成員——一個(gè)指向其實(shí)現(xiàn)類(lèi)( PersonImpl )的指針(這里是一個(gè) tr1::shared_ptr ,參見(jiàn)第 13 條),其他什么也沒(méi)有。我們通常將這樣的設(shè)計(jì)稱(chēng)為 pimpl idiom (指向?qū)崿F(xiàn)的指針)。在這樣的類(lèi)中,指針名通常為 pImpl ,就像上面代碼中一樣。

            通過(guò)這樣的設(shè)計(jì), Person 的客戶(hù)端程序員將會(huì)與日期、地址和人這些信息隔離開(kāi)。你可以隨時(shí)修改這些類(lèi)的具體實(shí)現(xiàn),但是 Person 的客戶(hù)端程序員不需要重新編譯。另外,由于客戶(hù)端程序員無(wú)法得知 Person 的具體實(shí)現(xiàn)細(xì)節(jié),他們就不容易編寫(xiě)出依賴(lài)于這些細(xì)節(jié)的代碼。這樣做真正起到了分離接口和實(shí)現(xiàn)的目的。

            這項(xiàng)分離工作的關(guān)鍵所在,就是用聲明的依賴(lài)來(lái)取代定義的依賴(lài)。這就是最小化編譯依賴(lài)的核心所在:只要可行,就要將頭文件設(shè)計(jì)成自給自足的,如果不可行,那么就依賴(lài)于其他文件中的聲明語(yǔ)句,而不是定義。其他一切事情都應(yīng)遵從這一基本策略。于是有:

            只要使用對(duì)象的引用或指針可行時(shí),就不要使用對(duì)象。 只要簡(jiǎn)單地通過(guò)類(lèi)型聲明,你就可以定義出類(lèi)型的引用和指針。反觀定義類(lèi)型對(duì)象的情形,你就必須要進(jìn)行類(lèi)型定義了。

            只要可行,就用類(lèi)聲明依賴(lài)的方式取代類(lèi)定義依賴(lài)。 請(qǐng)注意你在使用一個(gè)類(lèi)時(shí),如果你需要聲明一個(gè)函數(shù),那么在任何情況下定義出這個(gè)類(lèi)都不是必須的。即使這個(gè)函數(shù)以傳值方式傳遞或返回這個(gè)類(lèi)的對(duì)象:

            class Date;                     // 類(lèi)聲明

             

            Date today();                   // 這樣是可行的

            void clearAppointments(Date d);// 但并沒(méi)有必要對(duì) Date 類(lèi)做出定義

            當(dāng)然,傳值方式在通常情況下都不會(huì)是優(yōu)秀的方案,但是如果你發(fā)現(xiàn)某些情景下不得不使用傳值方式時(shí),就會(huì)引入不必要的編譯依賴(lài),你依然難擇其咎。

            在不定 Date 的具體實(shí)現(xiàn)的情況下,就可以聲明 today clearAppointments C++ 的這 一能力恐怕會(huì)讓你感到吃驚,但是實(shí)際上這一行為又沒(méi)有想象中那么古怪。如果代碼中任意一處調(diào)用了這些函數(shù),那 么在這次調(diào)用前的某處必須要對(duì) Date 進(jìn)行 定義。此時(shí)你又有了新的疑問(wèn):為什么我們要聲明沒(méi)有人調(diào)用的函數(shù)呢 , 這不是多此一舉嗎?這一疑問(wèn)的答案很簡(jiǎn)單:這種函數(shù)并不是沒(méi)有人調(diào)用,而是不是所有人都會(huì)去調(diào)用。假設(shè)你的庫(kù)中包含許多函數(shù)聲明,這并不意味著每一位客戶(hù)端程序員都會(huì)使用到所有的函數(shù)。上文的做法中,提供類(lèi)定義的職責(zé)將從頭文件中的函數(shù)聲明轉(zhuǎn)向客戶(hù)端文件中包含的函數(shù)調(diào)用,通過(guò)這一過(guò)程,你就排除了手工造成的客戶(hù)端類(lèi)定義依賴(lài),這些依賴(lài)實(shí)際上是多余的。

            為聲明和定義分別提供頭文件。 為了進(jìn)一步貫徹上文中的思想,頭文件必須要一分為二:一個(gè)存放聲明,另一個(gè)存放定義。當(dāng)然這些文件必須保持相互協(xié)調(diào)。如果某處的一個(gè)聲明被修改了,那么相應(yīng)的定義處就必須做出相應(yīng)的修改。于是,庫(kù)的客戶(hù)端程序員就應(yīng)該始終使用 #include 指令 來(lái)包含一個(gè)聲明頭文件,而不是自己進(jìn)行前置聲明,類(lèi)創(chuàng)建者應(yīng)提供兩個(gè)頭文件。比如說(shuō) ,在 Date 客戶(hù)端程序員需要聲明 today clearAppointments 時(shí),就應(yīng)該無(wú)需向上文中那樣, 對(duì) Date 進(jìn) 行前置聲明。更好的方案是用 #include 指令來(lái)引入恰當(dāng)?shù)穆暶黝^文件:

            #include "datefwd.h"        // 包含 Date 類(lèi)聲明 ( 而不是定義 ) 的頭文件

             

            Date today();              // 同上

            void clearAppointments(Date d);

            頭文件“ datefwd.h ”中僅包含聲明,這一名字來(lái)源于 C++ 標(biāo)準(zhǔn)庫(kù)中的 <iosfwd> (參見(jiàn)第 54 條)。 <iosfwd> 包含著 IO 流組件的聲明,這些 IO 流組件相應(yīng)的定義分別存放在不同的幾個(gè)頭文件中,包括: <sstream> 、 <streambuf> 、 <fstream> 以及 <iostream>

            從另一個(gè)角度來(lái)講,使用 <iosfwd> 作示例也是頗有裨益的,因?yàn)樗嬖V我們本節(jié)中的建議不僅對(duì)非模板的類(lèi)有效,而且對(duì)模板同樣適用。盡管在第 30 條中分析過(guò),在許多構(gòu)建環(huán)境中,模板定義通常保存在頭文件中,一些構(gòu)建環(huán)境中還是允許將模板定義放置在非頭文件的代碼文件里,因此提供為模板提供僅包含聲明的頭文件并不是沒(méi)有意義的。 <iosfwd> 就是這樣一個(gè)頭文件。

            C++ 提供了 export 關(guān)鍵字,它用于分離模板聲明和模板定義。但是遺憾的是,編譯器對(duì) export 的支持是十分有限的,實(shí)際操作中 export 更似雞肋。因此在高效 C++ 編程中, export 究竟扮演什么角色,討論這個(gè)問(wèn)題還為時(shí)尚早。

            諸如 Person 此類(lèi)使用 pimpl idiom 的類(lèi)通常稱(chēng)為句柄類(lèi)。為了避免你對(duì)這樣的類(lèi)如何完成這些工作產(chǎn)生疑問(wèn),一個(gè)途徑就是將類(lèi)中所有的函數(shù)調(diào)用放在相關(guān)的具體實(shí)現(xiàn)類(lèi)之前,并且讓這些具體實(shí)現(xiàn)類(lèi)去做真實(shí)的工作。請(qǐng)看下面的示例,其中演示了 Person 的成員函數(shù)應(yīng)該如何實(shí)現(xiàn):

            #include "Person.h"        // 我們將編寫(xiě) Person 類(lèi)的具體實(shí)現(xiàn),

                                       // 因此此處必須包含類(lèi)定義。

             

            #include "PersonImpl.h"    // 同時(shí),此處必須包含 PersonImpl 的類(lèi)定義,

                                       // 否則我們將不能調(diào)用它的成員函數(shù);請(qǐng)注意,

                                       // PersonImpl 擁有與 Person 完全一致的成員

                                       // 函數(shù) - 也就是說(shuō),它們的接口是一致的。

             

            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();

            }

            請(qǐng)注 意下面兩個(gè)問(wèn)題: Person 的構(gòu)造函數(shù)是如何調(diào)用 PersonImpl 的構(gòu)造函 數(shù)的(通過(guò)使 new - 參見(jiàn)第 16 條),以及 Person::name 是如何調(diào)用 PersonImpl :: name 的。這兩點(diǎn)很重要。將 Person 定制為一個(gè)句柄類(lèi)并不會(huì)改變它所做的事情,這樣做僅僅改變它做事情的方式。

            除了句柄類(lèi)的方法,我們還可以采用一種稱(chēng)為“接口類(lèi)”的方法來(lái)講 Person 定制為特種的抽象基類(lèi)。這種類(lèi)的目的就是為派生類(lèi)指定一個(gè)接口(參見(jiàn)第 34 條)。于是,通常情況下它沒(méi)有數(shù)據(jù)成員,沒(méi)有構(gòu)造函數(shù),但是擁有一個(gè)虛析構(gòu)函數(shù)(參見(jiàn)第 7 條),以及一組指定接口用的純虛函數(shù)。

            接口類(lèi)與 Java .NET 中的接口一脈相承,但是 C++ 并沒(méi)有像 Java .NET 中那樣對(duì)接口做出非常嚴(yán)格的限定。比如說(shuō),無(wú)論是 Java 還是 .NET 都不允許接口中出現(xiàn)數(shù)據(jù)成員或者函數(shù)實(shí)現(xiàn),但是 C++ 對(duì)這些都沒(méi)有做出限定。 C++ 所擁有的更強(qiáng)的機(jī)動(dòng)靈活性是非常有用的。就像第 36 條中所解釋的那樣,由于非虛函數(shù)的具體實(shí)現(xiàn)對(duì)于同一層次中所有的類(lèi)都應(yīng)該保持一致,因此不妨將這些函數(shù)實(shí)現(xiàn)放置在聲明它們的接口類(lèi)中,這樣做是有意義的,

            Person 的接口類(lèi)可以是這樣的:

            class Person {

            public:

             virtual ~Person();

             

             virtual std::string name() const = 0;

             virtual std::string birthDate() const = 0;

             virtual std::string address() const = 0;

             ...

            };

            這個(gè)類(lèi)的客戶(hù)端程序員必須要基于 Person 的指針和引用來(lái)編寫(xiě)程序,因?yàn)閷?shí)例化一個(gè)包含純虛函數(shù)的類(lèi)是不可能的。(然而,實(shí)例化一個(gè)繼承自 Person 的類(lèi)卻是可行的—參見(jiàn)下文。)就像句柄類(lèi)的客戶(hù)端程序員一樣,接口類(lèi)客戶(hù)端程序員除非遇到接口類(lèi)的接口有改動(dòng)的情況,其他任何情況都不需要對(duì)代碼進(jìn)行重新編譯。

            接口類(lèi)的客戶(hù)端程序員必須有一個(gè)創(chuàng)建新對(duì)象的手段。通常情況下,它們可以通過(guò)調(diào)用真正被實(shí)例化的派生類(lèi)中的一個(gè)函數(shù)來(lái)實(shí)現(xiàn),這個(gè)函數(shù)扮演的角色就是派生類(lèi)的構(gòu)造函數(shù)。這樣的函數(shù)通常被稱(chēng)作工廠(chǎng)函數(shù)(參見(jiàn)第 13 條)或者虛構(gòu)造函數(shù)。這種函數(shù)返回一個(gè)指向動(dòng)態(tài)分配對(duì)象的指針(最好是智能指針—參見(jiàn)第 18 條),這些動(dòng)態(tài)分配的對(duì)象支持接口類(lèi)的接口。這樣的函數(shù)通常位于接口類(lèi)中,并且聲明為 static 的:

            class Person {

            public:

             ...

             

              static std::tr1::shared_ptr<Person>// 返回一個(gè) tr1::shared_ptr ,

               create(const std::string& name,   // 它指向一個(gè) Person 對(duì)象,這個(gè)

                      const Date& birthday,       // Person 對(duì)象由給定的參數(shù)初始化,

                      const Address& addr);       // 為什么返回智能指針參見(jiàn)第 18

             ...

            };

            客戶(hù)端程序員這樣使用:

            std::string name;

            Date dateOfBirth;

            Address address;

            ...

             

            // 創(chuàng)建一個(gè)支持 Person 接口的對(duì)象

            std::tr1::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));

             

            ...

             

            std::cout << pp->name()         // 通過(guò) Person 的接口使用這一對(duì)象

                      << " was born on "

                       << pp->birthDate()

                      << " and now lives at "

                      << pp->address();

            ...                              // 當(dāng)程序執(zhí)行到 pp 的作用域之外時(shí),

                                            // 這一對(duì)象將被自動(dòng)刪除—參見(jiàn)第 13

            當(dāng)然,與此同時(shí),必須要對(duì)支持接口類(lèi)的接口的具體類(lèi)進(jìn)行定義,并且必須有真實(shí)的構(gòu)造函數(shù)得到調(diào)用。比如說(shuō),接口類(lèi) Person 必須有一個(gè)具體的派生類(lèi) RealPerson ,它應(yīng)當(dāng)為其繼承而來(lái)的虛函數(shù)提供具體實(shí)現(xiàn):

            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;      // 這里省略了這些函數(shù)的具體實(shí)現(xiàn),

             std::string birthDate() const;// 但是很容易想象它們是什么樣子。

             std::string address() const;

             

            private:

             std::string theName;

             Date theBirthDate;

             Address theAddress;

            };

            RealPerson ,編寫(xiě) Person::create 就如 探囊取物一般:

            std::tr1::shared_ptr<Person> Person::create(const std::string& name,

                                                        const Date& birthday,

                                                        const Address& addr)

            {

             return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday,addr));

            }

            Person::create 還有可以以一個(gè)更加貼近現(xiàn)實(shí)的方法來(lái)實(shí)現(xiàn),它應(yīng)能夠創(chuàng)建不同種類(lèi)的派生類(lèi)對(duì)象,創(chuàng)建的過(guò)程基于某些相關(guān)信息,例如:新加入的函數(shù)的參數(shù)值、從一個(gè)文件或數(shù)據(jù)庫(kù)中得到讀到的數(shù)值,環(huán)境變量,等等。

            RealPerson 向我們展示了實(shí)現(xiàn)接口類(lèi)的兩種通用的實(shí)現(xiàn)機(jī)制之一:它的接口規(guī)范繼 承自接口 類(lèi)( Person ,然后實(shí)現(xiàn)接口中的函數(shù)。第二種實(shí)現(xiàn)接口類(lèi)的方法牽扯到多重繼承,那是第 40 條中探索的主題。

            句柄類(lèi)和接口類(lèi)將接口從實(shí)現(xiàn)中分離開(kāi)來(lái),因此降低了文件間的編譯依賴(lài)。如果你是一個(gè)喜歡吹毛求疵的人,那么你一定又在想法挖苦本屆的思想了:“做了這么多變魔術(shù)般古怪的事情,我又能得到什么呢?”這個(gè)問(wèn)題的答案就是計(jì)算機(jī)科學(xué)中極為普遍的一個(gè)議題:你的程序在運(yùn)行時(shí)更慢了一步,另外,每個(gè)對(duì)象所占的空間更大了一點(diǎn)。

            使用句柄類(lèi)的情況下,成員函數(shù)必須通過(guò)實(shí)現(xiàn)指針來(lái)取得對(duì)象的數(shù)據(jù)。這樣無(wú)形中增加了每次訪(fǎng)問(wèn)時(shí)迂回的層數(shù)。同時(shí),實(shí)現(xiàn)指針?biāo)赶虻膶?duì)象所占的空間更大了一些,你必須要考慮這一問(wèn)題。最后,你必須要對(duì)實(shí)現(xiàn)指針進(jìn)行初始化(在句柄類(lèi)的構(gòu)造函數(shù)中),以便于將其指向一個(gè)動(dòng)態(tài)分配的實(shí)現(xiàn)對(duì)象,于是你就必須自己承擔(dān)動(dòng)態(tài)內(nèi)存分配(以及相關(guān)的釋放)內(nèi)在的開(kāi)銷(xiāo)以及遭遇 bad_alloc (內(nèi)存越界)異常的可能性。

            由于對(duì)于接口類(lèi)來(lái)說(shuō)每次函數(shù)調(diào)用都是虛擬的,因此你在每調(diào)用一次函數(shù)的過(guò)程中你就會(huì)為其付出一次間接跳轉(zhuǎn) 的代價(jià)(參見(jiàn)第 7 條)。同時(shí),派生自接口類(lèi)的對(duì)象必須包含一個(gè)虛 函數(shù)表指針(依然參見(jiàn)第 7 條)。這一指針也可能會(huì)使保存一個(gè)對(duì)象所需要的空間加大,這取決于接口類(lèi)是否是該對(duì)象中虛函數(shù)的唯一來(lái)源。

            最后,無(wú)論是句柄類(lèi)還是接口類(lèi),都不適合于過(guò)多使用內(nèi)聯(lián)。句柄和接口類(lèi)都是特別設(shè)計(jì)用來(lái)隱藏諸如函數(shù)體等具體實(shí)現(xiàn)內(nèi)容的。

            然而,僅僅由于句柄類(lèi)和接口類(lèi)會(huì)帶來(lái)一些額外的開(kāi)銷(xiāo)而遠(yuǎn)離它們,這樣的做法存在致命的錯(cuò)誤。虛函數(shù)也一樣,你并不希望忽略這些問(wèn)題,是嗎?(如果你真希望忽略些問(wèn)題,那么你可能看錯(cuò)書(shū)了。)你應(yīng)該把使用這些技術(shù)看作一個(gè)革命性的手段。在開(kāi)發(fā)過(guò)層中,使用句柄類(lèi)和接口類(lèi),來(lái)減少在具體實(shí)現(xiàn)有改動(dòng)時(shí)為客戶(hù)端程序員帶來(lái)的影響。在程序的速度和 / 或大小的變動(dòng)太大,足以體現(xiàn)出類(lèi)之間所增加的耦合度時(shí),還是可以適時(shí)使用具體的類(lèi)來(lái)取代句柄類(lèi)和接口類(lèi)。

            銘記在心

            最小化編譯依賴(lài)的基本理念就是使用聲明依賴(lài)代替定義依賴(lài)?;谶@一理念有兩種實(shí)現(xiàn)方式,它們是:句柄類(lèi)和接口類(lèi)。

            庫(kù)頭文件必須以完整、并且僅存在聲明的形式出現(xiàn)。無(wú)論是否涉及模板。

             

            中文成人久久久久影院免费观看 | 国产精品免费久久| 五月丁香综合激情六月久久| 污污内射久久一区二区欧美日韩| 2020最新久久久视精品爱| 国内精品久久国产大陆| 国产成人久久AV免费| 精品久久久久久无码专区| 日产精品99久久久久久| 久久精品中文闷骚内射| 久久精品国产99久久无毒不卡| 久久国产精品99精品国产| 国内精品伊人久久久久av一坑| 久久久国产乱子伦精品作者| 99久久久精品| 成人国内精品久久久久影院VR| 99热成人精品免费久久| 国产亚洲精午夜久久久久久 | 久久久久亚洲av成人网人人软件| 久久伊人精品青青草原日本| 伊人久久一区二区三区无码| 国产69精品久久久久APP下载| 国产69精品久久久久9999APGF| 午夜久久久久久禁播电影| 大伊人青草狠狠久久| 国产福利电影一区二区三区,免费久久久久久久精 | 久久精品国产一区二区| 一本一道久久a久久精品综合| 日产精品久久久久久久| 久久久一本精品99久久精品88| 久久久久国产精品| 国内精品久久久久国产盗摄| 久久久久黑人强伦姧人妻| 久久人人爽人人爽人人片AV东京热 | 亚洲国产精品一区二区三区久久 | 亚洲成色999久久网站| 久久国产V一级毛多内射| 久久久久久精品免费免费自慰| 久久久噜噜噜久久熟女AA片| 国产精品热久久毛片| 蜜桃麻豆WWW久久囤产精品|