SGI STL學習筆記(3):copy演算法實現細節
引言
copy()的函式原型如下:
template<class InputIterator, class OutputIterator>
inline OutputIterator copy(
InputIterator first,
InputIterator last,
OutputIterator result
);
引數:
first, last
指出被複制的元素的區間範圍[first,last)
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原始碼剖析》侯捷 著
相關文章
- SGI STL學習筆記(1):空間配置器(allocator)筆記
- SGI STL學習筆記(2):traits程式設計技法筆記AI程式設計
- SGI STL 的記憶體管理記憶體
- LevelDB學習筆記 (2): 整體概覽與讀寫實現細節筆記
- STL的學習筆記之一 (轉)筆記
- STL的內觀排序(introsort)演算法學習筆記 (轉)排序ROS演算法筆記
- Linux學習筆記 檔案讀寫小細節Linux筆記
- C++ 學習筆記之——STL 庫 queueC++筆記
- C++ 學習筆記之 STL 佇列C++筆記佇列
- Attention機制全流程詳解與細節學習筆記筆記
- C++學習筆記 — STL標準模板庫C++筆記
- ffmpeg學習筆記1----->udp流多路節目實現分離筆記UDP
- 演算法學習筆記演算法筆記
- Vue學習筆記3Vue筆記
- Thymeleaf 3學習筆記筆記
- CCNA學習筆記3筆記
- mysql學習筆記3MySql筆記
- Vue 3 學習筆記Vue筆記
- 學習記錄Spring Boot 記錄配置細節Spring Boot
- Python第一節學習筆記Python筆記
- 強化學習-學習筆記3 | 策略學習強化學習筆記
- 習題筆記 錢能 第3章節筆記
- LMF演算法學習筆記演算法筆記
- 機器學習演算法學習筆記機器學習演算法筆記
- 匈牙利演算法學習筆記演算法筆記
- 學習筆記----KM演算法筆記演算法
- 學習筆記----RMQ演算法筆記MQ演算法
- EM演算法學習筆記演算法筆記
- Floyd演算法學習筆記演算法筆記
- Tarjan 演算法學習筆記演算法筆記
- 【Go學習筆記13】介面和實現Go筆記
- MySql學習筆記--詳細整理--下MySql筆記
- swift學習筆記《3》-技巧Swift筆記
- tensorflow學習筆記3筆記
- docker學習筆記(3)- 映象Docker筆記
- Android學習筆記(3)Android筆記
- Python學習筆記(3)Python筆記
- PL/SQL學習筆記-3SQL筆記