【STL】演算法 — copy
阿新 • • 發佈:2019-01-02
為了效率,copy演算法可謂無所不用其極,通過分析copy演算法能夠體會STL的精妙。
首先是三個對外介面:
如果傳入的迭代器是字元型的原生指標,那麼直接使用底層的memmove拷貝,效率是非常高的,但如果是普通的迭代器,則需要進一步分析了。再看看__copy_dispatch函式,此函式又兵分三路,包括一個泛化版本和兩個偏特化版本:
首先分析泛化版本。如果迭代器仍為普通迭代器,則呼叫泛化版本。它要根據迭代器的型別(輸入或隨機)呼叫不同的函式:
由於輸入迭代器的移動只能靠operator++,所以採用逐個賦值。如果是隨機迭代器,則繼續往下呼叫:
這裡之所以要再單獨定義一個函式是因為下述的原生指標版本也可能會呼叫它。由於是隨機迭代器,所以它是以n是否大於0為迴圈判斷條件。相比於輸入迭代器的判斷條件first != last,這個版本顯然效率是要高一些的。這就充分利用了迭代器之間的區別儘可能的進行效率優化。 下面分析兩個特化版本。當迭代器為原生指標時,呼叫__copy_t,它的第三個引數是用來判斷指標所指型別是否真的需要用複製操作符來一個個複製(也就是判斷是trivial還是non-trivial),這種判斷工作就交給了型別萃取器__type_traits來完成。 根據是否需要單獨複製可以把__copy_t分成兩個版本:
trivial版本就很容易了,直接memmove,不需要做過多的複製動作。而non-trivial版本的拷貝則需要逐一進行。由於原生指標屬於隨機迭代器,所以它可以退而求其次,呼叫剛才介紹的__copy_d函式。 至此,一個既支援泛化,又具有極高效率的copy函式誕生了! 參考: 《STL原始碼剖析》 P314.
template <class InputIterator, class OutputIterator> // 泛化版本 inline OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result) { return __copy_dispatch<InputIterator,OutputIterator>()(first, last, result); } inline char* copy(const char* first, const char* last, char* result) { // 針對原生指標的過載 memmove(result, first, last - first); return result + (last - first); } 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拷貝,效率是非常高的,但如果是普通的迭代器,則需要進一步分析了。再看看__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)); } }; 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_t(first, last, result, 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_t(first, last, result, t()); } };
首先分析泛化版本。如果迭代器仍為普通迭代器,則呼叫泛化版本。它要根據迭代器的型別(輸入或隨機)呼叫不同的函式:
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; } 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)); }
由於輸入迭代器的移動只能靠operator++,所以採用逐個賦值。如果是隨機迭代器,則繼續往下呼叫:
template <class RandomAccessIterator, class OutputIterator, class Distance>
inline OutputIterator
__copy_d(RandomAccessIterator first, RandomAccessIterator last,
OutputIterator result, Distance*)
{
for (Distance n = last - first; n > 0; --n, ++result, ++first)
*result = *first;
return result;
}
這裡之所以要再單獨定義一個函式是因為下述的原生指標版本也可能會呼叫它。由於是隨機迭代器,所以它是以n是否大於0為迴圈判斷條件。相比於輸入迭代器的判斷條件first != last,這個版本顯然效率是要高一些的。這就充分利用了迭代器之間的區別儘可能的進行效率優化。 下面分析兩個特化版本。當迭代器為原生指標時,呼叫__copy_t,它的第三個引數是用來判斷指標所指型別是否真的需要用複製操作符來一個個複製(也就是判斷是trivial還是non-trivial),這種判斷工作就交給了型別萃取器__type_traits來完成。 根據是否需要單獨複製可以把__copy_t分成兩個版本:
template <class T>
inline T* __copy_t(const T* first, const T* last, T* result, __true_type) { // trivial
memmove(result, first, sizeof(T) * (last - first));
return result + (last - first);
}
template <class T>
inline T* __copy_t(const T* first, const T* last, T* result, __false_type) { // non-trivial
return __copy_d(first, last, result, (ptrdiff_t*) 0);
}
trivial版本就很容易了,直接memmove,不需要做過多的複製動作。而non-trivial版本的拷貝則需要逐一進行。由於原生指標屬於隨機迭代器,所以它可以退而求其次,呼叫剛才介紹的__copy_d函式。 至此,一個既支援泛化,又具有極高效率的copy函式誕生了! 參考: 《STL原始碼剖析》 P314.