1. 程式人生 > >C++ Traits程式設計技法--從迭代器的設計看引數推導與型別推導

C++ Traits程式設計技法--從迭代器的設計看引數推導與型別推導

迭代器與相應型別推導

在寫作泛型函式或程式碼時,我們可能存在這樣的需要:與引數相關的其它型別,比如一個迭代器的值的型別,在演算法中運用迭代器時,很可能會用到其也叫相應型別(associate type)。

什麼是相應型別?
迭代器所指之物的型別就是其中一個。如果我們的演算法中有必要宣告一個變數,以”迭代器所指物件的類別”為型號。

本文要向大家展示一個函式模板推導機制使用技法,這個在STL的迭代器和許多排序演算法中廣泛使用。

考慮一個情況,我們在寫一個泛型函式,它接受一對迭代器,要做的事就是對這一對迭代器之間的元素進行排序,其中將出現這幕:我需要對兩個值進行交換。不知道大家有沒有寫過這樣的程式碼,現在的問題是如何實現這兩個值的交換?

如:

if(*itr > *(itr+1))
{
    //交換兩個迭代器指向的值
    //注意此時我們並不(*itr)的型別
}

很顯然我們需要了解到迭代器所指向的具體型別,這該怎麼好呢

畢竟如果C++沒有隻支援sizeof(),並不支援typeof()

有些人說了我們有RTT1機制中的typeid(),是的很好,但是那獲取到的也只是型別名稱name,而不是型別type,更不可能來做變數宣告

關於RTT1機制中的typeid(),請參見C++ typeid關鍵字詳解

解決方法一函式模版的引數推導機制

解決的方法:利用函式模版(function template)的引數推導(argument deducation)機制。

示例

///理解模板型別推導
#include <iostream>

template <class Iter, class T>
void func_impl(Iter iter, T t)

{
    T tmp;              //  這裡解決了問題, T就是迭代器所指之物的型別, 本例為int

    /// ...  這裡做原來func應該做的全部工作
}




template<class Iter>
inline func(Iter iter)
{
    func_impl(iter, *iter);
}


int main( )
{
    int
i; func(&i); }

解析

引數推導機制,以func()為對外介面,卻把實際的操作全部置於func_impl()之中,由於func_impl()是一個函式模版(function template),一旦被呼叫,編譯器會自動進行template的引數推導,於是匯出型別T的問題,順利得到了解決。

問題

但是迭代器相應型別不只是“迭代器所指物件的型別”一種而已

根據經驗,最常用的相應型別有5種,然而並非任何情況下任何一種都可利用函式模版的引數推導機制來取得。

因此我們需要更一般的解法。

解決方法二使用巢狀依賴型別(nested depended name)

也稱為Traits程式設計技法,是STL中廣泛使用的技巧

迭代器所指物件的型別,稱為迭代器的value type,上述的引數型別推導技巧雖然可用於value type,卻並不對所有的相應型別可用。

前面的情況,通過隱藏了中間層,通過函式模版自己去推導引數型別,我們的介面也只是關心迭代器而不在意其指向的,但是如果我們的對外介面不能不瞭解到其相應型別的時候,這種方法就不適用了。

比如如下情況,萬一value type必須用於函式的返回值,就束手無策了。

畢竟函式的“template引數推導機制”推而導之的只是引數,無法推導函式的回返值型別。

我們需要其他方法,有什麼好的方法呢,還記得之前提到的typename的以及巢狀依賴型別(nested depended name)麼。

那不就可以麼,我們不妨一試

示例

#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

    T& operator*( ) const
    {
        return *m_ptr;
    }



    typedef T value_type;       //  內嵌型別宣告{nested type}
    T   *m_ptr;
};


template <class Iter>
typename Iter::value_type          ///  這一整行func的返回值型別
func(Iter iter)
{
    ///
    return *iter;
}


int main(void)
{
    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;
    return 0;
}

解析

func()的返回值被定義為typename Iter::value_type

內嵌型別被定義為 typedef T value_type

由於T是一個template引數,在它被編譯器具體化之前,編譯器對T一無所知,換句話說,編譯器此時並不知道MyIter<T>::value_type代表的是一個型別還是一個成員函式或者說是一個數據成員,

而這個typename關鍵字的用意就在於告訴編譯器這個是一個型別,如敝才能順利通過編譯。

問題

這樣我們的問題似乎得到了解決,但是這裡有一個隱晦的陷阱:並不是所有的迭代器都是類class type,比如有原生的指標就不是。如果不是class type,就無法為它定義內嵌型別。

但是這顯然不是我們做模版類所期待的,在STL(甚至於整個泛型思維)絕對接受原生的指標作為一種迭代器,所有我們上面的方法還不夠,有沒有什麼方法可以讓上述的一般化概念針對特定情況(比如原生的指標型別)做特殊化處理呢?

特性萃取機器–偏序化實現支援原生型別

那麼這就是template partial specialization可以完成的

模版特化問題

如果class template擁有一個以上的template引數,那麼可以針對其中某個(或者多個,但是不能全部)的引數進行特化工作。

換句話說,我們可以在泛化設計中提供一個特化的版本。即通過將泛化版本中的某些template引數賦予明確的指定。

/// 泛化的C接收任意型別
template <typename T>
class C             //  這個泛化版本允許接受T為任意型別
{
    // NOP...
};


/// 特化的類C接收原生的指標作為物件
template <typename T>
class C<T*>             //  這個泛化版本適用於"T為原生指標的情況"
{
    //  T為原生指標便是T為任何型別的一個更進一步的條件限制
    // NOP...
};

通過針對任何template引數的模版,進行特化,得到一個限制了引數物件的特化版本。

有了這個利器,我們就可以解決前述的”內嵌型別”未能解決的問題,先前的問題是,原生的指標並非class,因此無法為他們定義內嵌型別。

現在我們可以通過對迭代器指標為引數的特化物件,來設計可以接收原生指標的特殊迭代器。

萬能的中間層

因此我們設計出一箇中間層,並新增一個value type內嵌物件

//  泛化的iterator_traits物件
template <typename Iter>
struct iterator_traits
{
    typedef typename I::value_type value_type;
};

這個所謂的traits迭代器說明,如果Iter定義有自己的value type,那麼就通過這個traits的作用,萃取出來的value_type成員就是Iter::value_type。

換句話說,如果I定義有自己的value type,那麼先前的那個func就成為

//  之前的func
template <class Iter>
typename Iter::value_type          ///  這一整行func的返回值型別
func(Iter iter)
{
    ///
    return *iter;
}


//  新的func
template <class Iter>
//typename Iter::value_type          ///  這一整行func的返回值型別
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}

特化中間層以支援原生指標

增加iterator_traits這一中間層,現在我們為iterator_traits新增特化版本。

// 特化的iterator_traits接收<T*>引數
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};

通過這種方式,原生的指標比如int *雖然不是類物件class type,亦可通過traits取其value type。

於是我們的迭代器支援原生的指標型別了。

但是這樣子夠了麼,請看下面的例子,

當我們使用指向const常數物件的指標時,看看發生了什麼

iterator_traits<const int *>::value_type

獲得到的是一個const int,而不是int。很顯然這個並不是我們所期望?

比如我們利用這種方式來宣告一個臨時變數,使其類別與迭代器的value type相同,而現在,宣告一個無法賦值(因const之故)的暫時變數,並沒有什麼卵用。

這一切的一切,就只是因為我們獲取到的迭代器的value type是一個pointer-to-const

進一步特殊const

// 特化的iterator_traits接收<const T*>引數, 萃取出一個T型
template<class T>
struct iterator_traits<const T *>   /
{
    typedef T value_type;
};

現在OK了,不管是針對迭代器MyIterm,還是原生的指標int ,甚至是const int ,都可以通過traits取出正確的value type

程式碼

///c++11 條款1:理解模板型別推導
///http://blog.csdn.net/coolmeme/article/details/43986163
///http://blog.csdn.net/shinehoo/article/details/5722362
///  STL原始碼剖析 PDF-119/534

#include <iostream>
#include <typeinfo>


#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

    T& operator*( ) const
    {
        return *m_ptr;
    }



    typedef T value_type;       //  內嵌型別宣告{nested type}
    T   *m_ptr;
};


///// 泛化的C接收任意型別
//template <typename T>
//class C             //  這個泛化版本允許接受T為任意型別
//{
//    // NOP...
//};
//
//
///// 特化的類C接收原生的指標作為物件
//template <typename T>
//class C<T*>             //  這個泛化版本適用於"T為原生指標的情況"
//{
//    //  T為原生指標便是T為任何型別的一個更進一步的條件限制
//    // NOP...
//};


//  泛化的iterator_traits物件
template <typename Iter>
struct iterator_traits
{
    typedef typename Iter::value_type value_type;
};

// 特化的iterator_traits接收<T*>引數, 萃取出一個T型別
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};

// 特化的iterator_traits接收<const T*>引數, 萃取出一個T型
template<class T>
struct iterator_traits<const T *>
{
    typedef T value_type;
};


template <class Iter>
//typename Iter::value_type          ///  這一整行func的返回值型別
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}



int main(void)
{

    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;


    std::cout <<typeid(iterator_traits< MyIter<int> >::value_type).name();
    std::cout <<typeid(iterator_traits<int *>::value_type).name();
    std::cout <<typeid(iterator_traits<const int *>::value_type).name();


    return 0;
}

最後通過traits這個特性萃取機角色,可以巧妙的萃取出各個迭代器的屬性。

STL中迭代器的設計

這點我們可以在STL原始碼的找到對應的設計,參見 stl_iterator_base.h
由於

#ifdef __STL_USE_NAMESPACES
template <class _Category, class _Tp, class _Distance = ptrdiff_t,
          class _Pointer = _Tp*, class _Reference = _Tp&>
struct iterator {
  typedef _Category  iterator_category;
  typedef _Tp        value_type;
  typedef _Distance  difference_type;
  typedef _Pointer   pointer;
  typedef _Reference reference;
};
#endif /* __STL_USE_NAMESPACES */

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION

template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};