SGI STL學習筆記(3):copy演算法實現細節

li27z發表於2017-03-14

引言

copy()的函式原型如下:

template<class InputIterator, class OutputIterator>  
inline OutputIterator copy(  
      InputIterator first,   
      InputIterator last,   
      OutputIterator result  
);

引數:
first, last
指出被複制的元素的區間範圍[firstlast)
result 
指出複製到的目標區間起始位置

返回值:
返回一個迭代器,指出已被複制元素區間的最後一個位置

其作用是將輸入區間 [ first,last ) 內的元素複製到輸出區間 [ result,result +( last - first ) ),即*(result + n)=*(first + n),賦值操作是向前推進的。

這個函式有非常多的特化與強化版本,用盡函式過載、型別特性、偏特化等程式設計技巧,會根據其所接收的迭代器特性呼叫合適的版本,可謂“強化效率無所不用其極”。

演算法總覽

這裡寫圖片描述

演算法實現細節

copy執行的是複製操作,而複製操作不外乎運用拷貝建構函式或賦值運算子(copy演算法用的是後者),但是某些元素型別擁有的是trivial賦值運算子,因此,如果能使用記憶體直接複製行為,便能夠節省大量時間。

1)copy()函式的特化版本
針對原生指標(可視為一種特殊的迭代器)const char*和const wchar_t*,進行記憶體直接拷貝操作:

// 特殊版本1,針對原生指標const char*
inline char* copy(const char* first, const char* last, char* result) {
    memmove(result, first, last - first);
    return result + (last - first);
}

// 特殊版本2,針對原生指標const wchar_t*
inline wchar_t* copy(const wchar_t* first, const wchar_t* last, wchar_t* result) {
    memmove(result, first, sizeof(wchar_t) * (last - first));
    return result + (last - first);
}

// memmove函式原型
// 作用是複製 src 所指的記憶體內容前 num 個位元組到 dest 所指的地址上
// 它和memcpy的作用是一樣的,唯一的區別是,當記憶體發生區域性重疊的時候,memmove保證拷貝的結果是正確的,memcpy不保證拷貝的結果的正確,但memcpy比memmove的速度要快一些
void *memmove(void *dest, const void *src, size_t num);

2)copy()函式的泛化版本
copy()函式的泛化版本中呼叫了一個__copy_dispatch()函式,此函式有一個完全泛化版本和兩個偏特化版本:

// 完全泛化版本
template<class InputIterator, class OutputIterator>
struct __copy_dispatch
{
    OutputIterator operator()(InputIterator first, InputIterator last, OutputIterator result)
    {
        return __copy(first, last, result, 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_trivial_assignment_operator t;   
        return __copy(first, last, result, t());
    }
};

// 偏特化版本2,第一個引數是const T*指標形式,第二個引數是T*指標形式
template<class T>
struct __copy_dispatch<const T*, T*>
{
    T* operator()(const T* first, const T* last, T* result)
    {
        typedef typename __type_traits<T>::has_trivial_assignment_operator t;   
        return __copy(first, last, result, t());
    }
};

首先來看__copy_dispatch()的完全泛化版本,它會根據迭代器的種類不同,呼叫不同的__copy(),如下:

// 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));       
}

// __copy_d()在其它地方也會用到
template <class RandomAccessIterator, class OutputIterator>
inline OutputIterator
__copy_d(RandomAccessIterator first, RandomAccessIterator last,
       OutputIterator result, Distance*)
{
    // 以n決定迴圈的次數,速度快
    for(Distance n = last - first; n > 0; --n, ++result, ++forst) {
        *result = *first;
    }
    return result;
}

再來看__copy_dispatch()的兩個偏特化版本。這兩個偏特化版本是在“引數為原生指標形式”的前提下,希望進一步探測“指標所指之物”是否具有平凡賦值操作符。因為複製操作是由賦值操作符來完成的,如果指標所指物件擁有非平凡賦值操作符,複製操作就一定要靠它來執行;但如果物件擁有的是平凡賦值操作符,就可以直接以最快的記憶體拷貝方式來執行。

// 指標所指的物件具有平凡賦值操作符
template<class T>
inline T* __copy_t(const T* first, const T* last, T* result, __true_type)
{
    memmove(result, first, szeof(T) * (last - first));
    return result + (last - first);
}

// 指標所指的物件具有非平凡賦值操作符
template<class T>
inline T* __copy_t(const T* first, const T* last, T* result, __false_type)
{
    return __copy_d(first, last, result, (ptrdiff_t*)0);
}

以上即為copy演算法實現細節。

備註

使用copy()時,如果輸入區間和輸出區間完全沒有重疊,當然毫無問題,否則需特別注意。copy演算法是一一進行元素的賦值操作,如果輸出區間的起點位於輸入區間內,copy演算法便(可能)會在輸入區間的(某些)元素尚未被複制之前,就覆蓋其值,導致錯誤結果。如果copy演算法根據其所接收的迭代器的特性決定呼叫memmove()來執行任務,就不會造成上述錯誤,因為memmove()會先將整個區間的內容複製下來,沒有被覆蓋的危險。

例:

#include <iostream>
#include <vector>
#include <deque>
#include <iterator>
#include <algorithm>

using namespace std;

int main()
{   
        int i;
        int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
        // 原生指標
        copy(ia + 2, ia + 7, ia + 4);
        for(i = 0; i < 9; i++)
        {
                cout << ia[i] << ' '; 
        }
        cout << endl;

        int ib[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
        vector<int> ivec(ib, ib + 9);
        vector<int>::iterator first = ivec.begin();
        vector<int>::iterator last = ivec.end();
        ++++first;
        ----last;
        vector<int>::iterator result = ivec.begin();
        ++++++++result;
        // vector的迭代器是原生指標
        copy(first, last, result);
        for(i = 0; i < 9; i++)
        {
                cout << ivec[i] << ' ';
        }
        cout << endl;

        int ic[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
        deque<int> id(ib, ib + 9);
        deque<int>::iterator firstD = id.begin();
        deque<int>::iterator lastD = id.end();
        ++++firstD;
        ----lastD;
        deque<int>::iterator resultD = id.begin();
        ++++++++resultD;
        // deque的迭代器被歸類於random access iterator
        copy(firstD, lastD, resultD);
        for(deque<int>::iterator it = id.begin(); it != id.end(); it++)
        {
                cout << *it << ' ';
        }
        cout << endl;
        return 0;
}

執行結果:
這裡寫圖片描述


參考資料:《STL原始碼剖析》侯捷 著

相關文章