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;
}
現在編譯器就知道了。