天天看點

Effective C++條款42:模闆與泛型程式設計——了解typename的雙重意義一、意義①二、意義②三、總結

一、意義①

  • 意義①:typename可以在template中聲明類型參數
  • 在template中聲明類型參數時,typename和class是等價的,兩者都可以
  • 例如:
//兩者是等價的

template<class T> class Widget;

template<typename T> class Widget;
           

二、意義②

  • 意義②:可以用來聲明某種類型

示範說明

  • 現在我們有一個模闆,其接受一個STL容器類型,然後列印容器中的第二個元素的值,但是這個模闆可能會産生錯誤。代碼如下:
template<typename C>

void print2nd(const C& container)

{

    if (container.size() >= 2) {

        C::const_iterator iter(container.begin()); //初始化疊代器,綁定到第一個元素上

        ++iter;

        int value = *iter;

        std::cout << value;

    }

}
           
  • 關于“嵌套從屬名稱”和“非從屬名稱”的概念:
    • 從屬名稱:
      • 對于上面的iter來說,其是容器container的疊代器類型const_iterator,其依賴于template參數C的類型,是以我們稱const_iterator為從屬名稱(意義為:模闆内的一個名稱依賴于template的某個參數,那麼其就是從屬名稱)
      • 當從屬名稱屬于class内時,我們稱其為嵌套從屬名稱。是以上面的const_iterator就是一種嵌套從屬名稱
    • 非從屬名稱:上面的value變量,其類型就是int,不依賴于其他任何東西,是以我們稱int為非從屬名稱
  • 上面的代碼之是以可能會産生錯誤,與從屬名稱有關系。我們現在考慮這樣的一個情況:
    • 我們修改print2nd模闆,假設在其中定義一個疊代器指針,代碼如下
    • 但是編譯器可能會這樣了解:const_iterator不是一個嵌套從屬名稱,而是一個類的static成員變量,x為一個局部變量。是以下面的代碼可能會被編譯器認為是兩個變量在相乘
template<typename C>

void print2nd(const C& container)

{

    //...

    //const_iterator可能被編譯器了解為C的static成員變量,x為一個變量,下面是兩個變量的相乘

    C::const_iterator* x;
    
    //...

}
           
  • 正确的做法是為嵌套從屬名稱加上一個關鍵字typename,這樣可以顯式地告訴編譯器某種東西是一個類型,而不是其他東西。代碼如下:
template<typename C>

void print2nd(const C& container)

{

    if (container.size() >= 2) {

        //使用typename,顯式告訴編譯器,const_iterator是一個類型

        typename C::const_iterator iter(container.begin());

        ++iter;

        int value = *iter;

        std::cout << value;
        
    }

}
           
  • 有了上面的規則之後,我們可以編寫下面的模闆函數,其接受一個容器類,和該容器的疊代器為參數。代碼如下:
template<typename C>

void print2nd(const C& container,typename C::const_iterator iter)

{

    //...

}
           

typename的一個例外

  • 當typename用來聲明類型時,其不可以出現在兩個地方:
    • ①基類的派生清單中
    • ②構造函數的成員初始化列中中
  • 我們假設Nested是基類中的一種類型。例如:
template<typename T>

class Derived :public Base<T>::Nested //此處不可以使用typename

{

public:

    explicit Derived(int)

    :Base<T>::Nested(x)//此處不可以使用typename

    {

        typename Base<T>::Nested temp; //此處可以使用typename

    }

};
           

typename在traits機制中的運用

  • 假設我們現在有這樣一個函數模闆,其接受一個疊代器類型,我們打算在函數中為疊代器所指的對象做一份副本。代碼如下:
template<typename IterT>

void workWithIterator(IterT iter)

{

    typename std::iterator_traits<IterT>::value_type temp(*iter);

    //...

}
           
  • 此處我們使用到了iterator_traits<>模闆類,其實一種traits類(參閱條款47)。我們傳遞給其一個疊代器類型為其進行執行個體化,那麼我們就可以通過其value_type萃取出疊代器所指的容器的類型。例如:
    • 如果IterT是list<string>::iterator,那麼value_type就代表string,temp的類型就是string
    • 如果IterT是vector<int>::iterator,那麼value_type就代表int,temp的類型就是int
  • 因為value_type也是一種内嵌類型,是以我們需要使用typename聲明其是一種類型
  • 如果上面的代碼比較複雜,那麼我們還可以搭配typedef來使用。例如:
template<typename IterT>

void workWithIterator(IterT iter)

{

    typedef typename std::iterator_traits<IterT>::value_type value_type; //為類型聲明别名

    value_type temp(*iter); //使用類型定義變量

    //...

}
           
  • 關于typename的移植性問題:某些編譯器接受typename,而可能有少數編譯器不接受typename。是以其存在有移植性

三、總結

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

繼續閱讀