1. 程式人生 > >STL(八)——演算法(Algorithm)(二):copy

STL(八)——演算法(Algorithm)(二):copy

STL包含眾多演算法,不過很多演算法實現其實很簡單,看原始碼即可理解。但也有部分簡單的函式,實現版本做了很多工作來加快效率,copy函式就是一個例子。我們挑sgi stl實現的copy函式作為例子談一談。

è¿éåå¾çæè¿°

概述

copy函式是呼叫比較頻繁的函式,由於copy進行的是複製操作,而複製操作不外乎運用賦值運算子或複製建構函式實現,但有但元素擁有trivial assignment operator,因此,如果能夠直接使用記憶體直接複製行為(如memmove,memcpy)能節省很多時間。為此SGI STL但copy演算法用了多種辦法,包括函式過載(function overloading)、型別特性(type traits)、偏特化(partial specialization)等程式設計技巧加強效率。上表就是copy函式一覽。

實現細節

copy演算法可以將輸入區間[first,last]內的元素複製到輸出區間[result, result+(last - first)。也就是說,它會執行賦值操作*result = *first , *(result+1)=*(first+1),...。返回一個迭代器result+(last-first)。copy對其template的引數非常寬鬆,其輸入區間只需要由InputIterator構成即可,輸出區間只需由ourputIterator構成即可。

copy函式涉及一個區間是否重疊的問題。如果輸入區間的頭部包含輸出區間的尾部,那麼正向賦值不會出現問題。但如果輸入區間的尾部包含輸出區間的頭部,那麼正向賦值會導致錯誤的賦值,這時候需要反過來賦值。

下面是部分原始碼

template<class InputIterator, class OutputIterator>
inline OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result)
{
    return __copy_dispatch<InputIterator, OutputIterator>()(first, last, result);
}


//特化版本 const char*
inline char* copy(const char* first, const char* last, char*result)
{
    memove(result, first, last-first);
    return result + (last - first);
}

//完全泛化版本
template<class InputIterator, class OutputIterator>
struct __copy_dispatch
{
    OutputIterator operator()(InputIterator first, InputIterator last, 
                                OutputIterator result)
{
    return __copy(first, last, iterator_category(first));
}
};

//偏特化版本1,兩個引數都是T*
template<class T>
struct __copy_dispatch<T*,T*>
{
    T* operator()(T* first, T* last, T* result)
{ typedef typename __type_traits<T>::has_trival_assignment_operator t;
    return __copy_t(first, last, result, t());}
};

這裡__copy_dispatch的完全泛化版本根據迭代器種類不同,呼叫來不同的_cop(),為的是不同迭代器所使用的迴圈條件不同,有快慢區別。

//InputIterator
template<class InputIterator, class OutputIterator>
inline OutputIterator __copy(InputIterator first, InputIterator last, OutputIterator result, input_iterator_tag)
{
    //以迭代器是否相等決定迴圈是否繼續,速度慢
    for(; first != last; ++result, ++first)
    {
        *result = *first;
    }
    return result;
}

//RandomAccessIterator版本
template<class RandomAccessIterator, class OutputIterator>
inline OutputIterator __copy(RandomAccessIterator first, 
                            RandomAccessIterator  last, 
                            OutputIterator result, 
                            random_access_iterator_tag)
{
    //又劃分出一個函式,為的是其他地方也能用到
    return __copy_d(first, last, result, distance_type(first));
}

template<class RAI, class OPI, class Distance>
inline OutputIterator __copy_d(RAI first, RAI last, OPI result, Distance*)
{
    //以n決定迴圈次數,速度快
    for(Distance n = last - first; n > 0; --n, ++result, ++first)
        *result = *first;
    return result;
}

上面是__copy_dispatch()完全泛化的內容。現在回到它的兩個特化版本。兩個偏特化版本在“引數是原生指標形式”的前提下,希望進一步探測“指標所指之物是否具有trivial assignment operator(平凡賦值操作符)。這對效率有很大影響,因為這裡的複製操作是由assignment負責的,如果指標所指向的物件擁有non_trivial assignment operator,複製操作就一定得通過它來執行。但如果指標指向但是trivial assignment operator的物件,複製操作就可以不通過它,直接以最快速的記憶體對拷方式(memmove)完成。c++語言無法讓你偵測到某個物件的型別是否具有trivial assignment operator, 但是SGI STL採用的type_trait技巧可以彌補。程式碼如下:

//以下版本適用於指標所指向之物有trivial assignment operator
template<T>
inline T* __copy_t(const T* first, const T* last, T* result, __true_type)
{
    memove(result, first, sizeof(T)*(last - first));
    return result + (last - first);
}

//非trivial assignment operator
template<T>
inline T* __copy_t(const T* first, const T* last, __false_type)
{
    return __copy_d(first, last, result, (ptrdiff_t*)0); //這裡傳0也是為來效率考慮,這樣
                                                         //可以少呼叫建構函式,而且讓編
                                                         //譯器知道型別              
}

現在又有一個問題。因為stl只記錄了c++的型別,而對我們自己定義的有trivial assignment operator的類沒有記錄,所以即使有triveal assignment operator ,c++也是按照non_trivral來呼叫的。那怎麼讓c++知道我們的類有trivial assignment operator呢?

我們需要將<type_trains.h>中的__type_traits<T>做一個特化版本:

__STL_TEMPLATE_NULL struct __type_traits<C>
{
    typedef __false_type has_trivial_default_constructor;
    typedef __true_type  has_trivial_copy_constructor;
    typedef __true_type  has_trivial_assignment_operator;
    typedef __true_type  has_trivral_destructor;
    typedef __true_type  is_POD_type;
}

現在編譯器就知道了。