《Effective C++》:條款41-條款42

來源:互聯網
上載者:User

標籤:c++   模板   templates   高效c++   typename   

  • 條款41瞭解隱式介面和編譯期多態
  • 條款42瞭解typename的雙重意義

條款41:瞭解隱式介面和編譯期多態

物件導向編程總是以顯示介面(explicit interfaces)和運行期多態(runtime polymorphism)來解決問題。例如

 class Widget{    public:        Widget();        virtual ~Widget();        virtual std::size_t size() const;        virtual void normalize();        void swap(Widget& other);        ……    };    void doProcessing(Widget& w)    {        if(w.size()>10 && w!=someNasyWidget)        {            Widget temp(w);            temp.normalize();            temp.swap(w);        }    }

可以這樣說doProcessing內的w

  • w的類型被聲明為Widget,所以w必須支援Widget介面。
  • Widget的某些成員函數是virtual,w對於這樣函數的調用將表現出運行期多態(runtime polymorphism)。

在Templates和泛型程式設計的世界中,顯示介面和運行期多態仍然存在,但是更要到的是隱式介面(implicit interface)和編譯器多態(compile-time polymorphism)。將doProcessing從函數變為函數模板(function template)

template<typename T>    void doProcessing(T& w)    {        if(w.size()>10 && w!=someNasyWidget)        {            Widget temp(w);            temp.normalize();            temp.swap(w);        }    }

現在再來看doProcessing內的w

  • w必須支援哪種介面,有template中執行於w身上的操作來決定。
  • 凡涉及w的任何函數調用,例如operator>和operator!=,有可能造成template的具現化(instantiated),使這些調用得以成功。這樣的局現化發生在編譯期。以不同template參數具現化function template會導致調用不同的函數,這就是編譯期多態(compile-time polymorphism)。

編譯期多態和運行期多態不難區分,現在來看顯式介面和隱式介面的區別。通常顯式介面有函數的簽名式(函數名稱、參數類型、傳回值)構成。

    class Widget{    public:        Widget();        virtual ~Widget();        virtual std::size_t size() const;        virtual void normalize();        void swap(Widget& other);        ……    };

public介面由一個建構函式、一個解構函式、函數size,normalize、swap以及其參數、傳回值、常量性(constness)構成,還包括編譯器產生的copy建構函式和copy assignment操作符。

隱式介面就完全不同,它不是由函數簽名決定,而是由有效運算式(valid expression)組成。

template<typename T>    void doProcessing(T& w)    {        if(w.size()>10 && w!=someNasyWidget)        {            Widget temp(w);            temp.normalize();            temp.swap(w);        }    }

可以看出T(w類型)的隱式介面好像有這些約束

  • 必須提供一個名為size的函數,該函數返回一個整數值
  • 必須支援一個operator!=汗還是,用來比較兩個對象。

其實並不是必須滿足這兩個約束。T必須支援size成員函數,但是這個函數可能從base class繼承。這個函數不需要返回一個整數值,甚至不需要返回一個數實值型別。甚至不需要返回一個定義有operator>的類型。它唯一要做的就是返回一個類型為X的對象,而X對象加上一個int(10的類型)必須能夠調用一個operator>。這個operator>不需要非得取得一個類型為X的參數,它可以取得類型為Y的參數,只要存在一個隱式轉換能夠將類型X的對象轉換為類型為Y的對象。同理T不需要支援operator!=。

以上分析還沒有考慮operator&&被重載,一個串連詞的改變或許完全不同的某種東西,可能改變上述運算式的意義。

第一次以此種方式思考隱式介面會感覺不習慣。隱式介面僅僅由一組有效運算式構成,這個運算式可能看起來很複雜,但它們要求的約束條件一般而言相當直接又明確,例如:

if(w.size()>10 && w!=someNasyWidget)

關於函數size、operator>、operator&&、operator!=身上的約束條件,很難再說太多;但整體確認運算式約束條件很容易。if運算式必須為布爾值,因此整體運算式必須與bool相容。這時template doProcessing中型別參數T隱式介面的一部分,doProcessing要求其他隱式介面:copy建構函式、normalize和swap也必須對T型對象有效。

加諸於template參數身上的隱式介面和加諸於class對象身上介面一樣真實,都是在編譯期完成檢查,如果template中使用不支援template所要求的隱式介面,代碼不能編譯通過

總結

  • class和template都支援介面和多態。
  • class的介面是顯式的,以函數簽名為中心,多態是通過virtual函數發生於運行期。
  • template的介面是隱式的,基於有效運算式。多態是通過template具體化和函數重載解析(function overloading resolution)發生在編譯期。
條款42:瞭解typename的雙重意義

使用模板時,可以用typename,也可以用class

 template<class T> class Widget;    template<typename T> class Widget;

兩者沒有什麼不同。作為template的型別參數,意義完全相同。在使用習慣上來說,很多人喜歡使用typename,因為這暗示參數並非一定要是個class類型。

C++有時不會把class和typename等價。有時候一定要用typename;為了瞭解原理,先來談談在template內指涉(refer to)的兩種名稱。

用一個例子來說明,現在有個template function,接收STL相容容器為參數,容器內對象可被賦值為int,這個函數列印第二個元素的值

 template<typename C>    void print2nd(const C& container)    {        if(container.size()>=2)        {            C::const_iterator iter(container.begin());            ++iter;            int valaue=*iter;            std::cout<<value;        }    }

現在解釋一下兩個local變數iterate和value。iterate類型為C::const_iterator,實際是什麼取悅於template參數C。template內出現的名稱如果依賴於某個template參數,稱這個名稱為從屬名稱(dependent names)。如果從屬名稱在class內呈嵌套狀,稱之為嵌套從屬名稱(nested dependent name)。C::const_iterator就是這樣的嵌套從屬名稱(nested dependent name),且指涉某類型。value是int類型,不依賴template參數,稱之為非從屬名稱(non-dependent names)。

嵌套從屬名稱有可能導致解析(parsing)困難,例如在print2dn這樣做

template<typename C>void print2nd(const C& container){    C::const_iterator* x;    ……}

表面上看是聲明了一個local變數x,x類型是只需C::const_iterator的指標,這是因為我們直到C::const_iterator是個類型。如果不是這樣呢?例如,C內有個static成員變數碰巧命名為const_iterator,或x是個global變數;這樣的話就是一個相乘動作:const::const_iterator乘以x。編寫C++解析器的人必須操心這樣的輸入。

在直到C是什麼之前,不能確定C::cont_iterator是否為一個類型。當編譯器解析template print2nd時,不知道C是什麼東西。C++有個規則可以解析這個歧義狀態:如果解析器在template中遭遇到一個嵌套從屬名稱,它便假設這個明白不是個類型,除非你告訴它是。所以預設情況下,嵌套從屬名稱不是類型。這個規則還有個例外,稍後再提。

再來看看print2nd,C::const_iterator iter只有在C::const_iterator是個類型時才合理,但是C++預設認為它不是。要矯正這個形式,我們必須告訴C++C::const_iterator是個類型,在其前面放置關鍵字typename即可

 template<typename C>    void print2nd(const C& container)    {        if(container.size()>=2)        {            typename C::const_iterator iter(container.begin());            ++iter;            int valaue=*iter;            std::cout<<value;        }    }

“typename必須作為嵌套從屬類型名稱的首碼詞”這一規則的例外是:typename不可以出現在base class list內的嵌套從屬類型名稱之前,也不可以在member initialization list(成員初值列)中作為base class修飾符。

 template<typename T>    class Derived: public Base<T>::Nested{ //base class list中不允許typename    public:        explicit Derived(int x)        :Base<T>::Nested(x)  //mem init.list中不允許typename        {            typename Base<T>::Nested temp; //嵌套從屬名稱類型            ……  //既不在base class list中,也不再mem init list中,作為base class修飾符要加上typename        }        ……    };

再來看一個typename例子。一個function template接受一個迭代器,我們打算為該迭代器指涉的對象做一份local複件(副本)temp

    template<typename IterT>    void workWithIterator(IterT iter)    {        typename::std::iterator_traits<IterT>::value_type temp(*iter);        ……    }

typename::std::iterator_traits<IterT>::value_type是標準traits class(**條款**47)的一種運用。std::iterator_traits<IterT>::value_type是個嵌套從屬類名稱,所以在其前面放置typename。如果使用std::iterator_traits<IterT>::value_type感覺太長不習慣,可以考慮建立一個typedef,對於traits成員名稱,普遍的習慣是設定typedef名稱代表某個traits成員名稱。

 template<typename IterT>    void workWithIterator(IterT iter)    {        typedef typename::std::iterator_traits<IterT>::value_type value_type;        value_type temp(*iter);        ……    }

最後提一下,typename相關規則在不同編譯器上有不同的實踐。這意味著typename和嵌套從屬名稱之間的互動,也許會在移植性方面帶來頭疼的問題。

總結

  • 聲明template參數時,首碼關鍵字class和typename可以互換。
  • 使用關鍵字typename標識嵌套從屬類型名稱;但不得在base class list(基類列)或member initialization list(成員初值列)內以它作為base class修飾符。

《Effective C++》:條款41-條款42

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.