C++ STL move_iterator移動迭代器用法詳解

2020-07-16 10:05:23
C++ 11 還為 STL 標準庫增添了一種疊代器介面卡,即本節要講的 move_iterator 移動迭代器介面卡

move_iterator 疊代器介面卡,又可簡稱為移動迭代器,其可以實現以移動而非複製的方式,將某個區域空間中的元素移動至另一個指定的空間。

舉個例子,前面講了 vector 容器,該型別容器支援如下初始化的方式(程式一):
#include <iostream>
#include <vector>
#include <list>
#include <string>
using namespace std;
int main()
{
    //建立並初始化一個 vector 容器
    vector<string> myvec{ "STL","Python","Java" };
    //再次建立一個 vector 容器,利用 myvec 為其初始化
    vector<string>othvec(myvec.begin(), myvec.end());
  
    cout << "myvec:" << endl;
    //輸出 myvec 容器中的元素
    for (auto ch : myvec) {
        cout << ch << " ";
    }
    cout << endl << "othvec:" << endl;
    //輸出 othvec 容器中的元素
    for (auto ch : othvec) {
        cout << ch << " ";
    }
    return 0;
}
程式執行結果為:

myvec:
STL Python Java
othvec:
STL Python Java

注意程式第 11 行,初始化 othvec 容器是通過複製 myvec 容器中的元素實現的。也就是說,othvec 容器從 myvec 容器中複製了一份 "STL"、"Python"、"Java" 並儲存起來,此過程不會影響 myvec 容器。

那麼,如果不想採用複製的方式,而就是想 myvec 容器中儲存的元素全部移動到 othvec 容器中,該怎麼辦呢?沒錯,就是採用移動迭代器。

值得一提的是,實現移動迭代器的模板類定義在 <iterator> 標頭檔案,並位於 std 名稱空間中。因此,在使用該型別疊代器時,程式中應包含如下程式碼:
#include <iterator>
using namespace std;
第二行程式碼不是必需的,但如果不用,則程式中在建立該型別的疊代器時,必須手動注明 std 名稱空間(強烈建議初學者使用)。

實現 move_iterator 移動迭代器的模板類定義如下:
template <class Iterator>
    class move_iterator;
可以看到,在使用此迭代器時,需要傳入一個基礎疊代器 Iterator。

注意,此基礎疊代器的型別雖然沒有明確要求,但該模板類中某些成員方法的底層實現,需要此基礎疊代器為雙向疊代器或者隨機存取疊代器。也就是說,如果指定的 Iterator 型別僅僅是輸入疊代器,則某些成員方法將無法使用。

實際上,在 move_iterator 模板類中就包含有指定 Iterator 型別的基礎疊代器,整個模板類也是借助此基礎疊代器實現的。關於 move_iterator 的底層實現,C++ STL move_iterator手冊給出了詳細的參考程式碼,有興趣的讀者可自行研究。

C++ STL move_iterator的建立

move_iterator 模板類中,提供了 4 種建立 move_iterator 疊代器的方法。

1) 通過呼叫該模板類的預設建構函式,可以建立一個不指向任何物件的移動迭代器。比如:
//將 vector 容器的隨機存取疊代器作為新建移動迭代器底層使用的基礎疊代器
typedef std::vector<std::string>::iterator Iter;
//呼叫預設建構函式,建立移動迭代器
std::move_iterator<Iter>mIter;

如果程式中引入了 std 名稱空間,則上面程式碼中所有的 std:: 都可以省略。

由此,我們就建立好了一個 mIter 移動迭代器,該疊代器底層使用的是 vector 容器的隨機存取疊代器,但這裡沒有為此基礎疊代器明確指向,所以 mIter 疊代器也不知向任何物件。

2) 當然,在建立 move_iterator 疊代器的同時,也可以為其初始化。比如:
//建立一個 vector 容器
std::vector<std::string> myvec{ "one","two","three" };
//將 vector 容器的隨機存取疊代器作為新建移動迭代器底層使用的基礎疊代器
typedef std::vector<std::string>::iterator Iter;
//建立並初始化移動迭代器
std::move_iterator<Iter>mIter(myvec.begin());
這裡,我們建立了一個 mIter 移動迭代器,同時還為底層使用的隨機存取疊代器做了初始化,即令其指向 myvec 容器的第一個元素。

3) move_iterator 模板類還支援用已有的移動迭代器初始化新建的同型別疊代器,比如,在上面建立好 mIter 疊代器的基礎上,還可以向如下這樣為新建的移動迭代器初始化:
std::move_iterator<Iter>mIter2(mIter);
//還可以使用 = 運算子,它們是等價的
//std::move_iterator<Iter>mIter2 = mIter;
這樣建立的 mIter2 疊代器和 mIter 疊代器完全一樣。也就是說,mIter2 底層會複製 mIter 疊代器底層使用的基礎疊代器。

4) 以上 3 種建立 move_iterator 疊代器的方式,其本質都是直接呼叫 move_iterator 模板類中的構造方法實現的。除此之外,C++ STL 標準庫還提供了一個 make_move_iterator() 函數,通過呼叫此函數可以快速建立一個 move_iterator 疊代器。

C++ STL 標準庫中,make_move_iterator() 是以函數模板的形式提供的,其語法格式如下:

template <class Iterator>
  move_iterator<Iterator> make_move_iterator (const Iterator& it);

其中,引數 it 為基礎疊代器,用於初始化新建疊代器。同時,該函數會返回一個建立好的移動迭代器。

舉個例子:
typedef std::vector<std::string>::iterator Iter;
std::vector<std::string> myvec{ "one","two","three" };
//將 make_move_iterator() 的返回值賦值給同型別的 mIter 疊代器
std::move_iterator<Iter>mIter = make_move_iterator(myvec.begin());
下面程式對程式一做了修改,即運用移動迭代器為 othvec 容器初始化:
#include <iostream>
#include <vector>
#include <list>
#include <string>
using namespace std;
int main()
{
    //建立並初始化一個 vector 容器
    vector<string> myvec{ "STL","Python","Java" };
    //再次建立一個 vector 容器,利用 myvec 為其初始化
    vector<string>othvec(make_move_iterator(myvec.begin()), make_move_iterator(myvec.end()));
   
    cout << "myvec:" << endl;
    //輸出 myvec 容器中的元素
    for (auto ch : myvec) {
        cout << ch << " ";
    }
    cout << endl << "othvec:" << endl;
    //輸出 othvec 容器中的元素
    for (auto ch : othvec) {
        cout << ch << " ";
    }
    return 0;
}
程式執行結果為:

myvec:

othvec:
STL Python Java

通過和程式一做對比不難看出它們的區別,由於程式第 11 行為 othvec 容器初始化時,使用的是移動迭代器,其會將 myvec 容器中的元素直接移動到 othvec 容器中。

注意,即便通過移動迭代器將容器中某區域的元素移動到了其他容器中,該區域內仍可能殘留有之前儲存的元素,但這些元素是不能再被使用的,否則極有可能使程式產生各種其他錯誤。


和其他疊代器介面卡一樣,move_iterator 模板類中也提供有 base() 成員方法,通過該方法,我們可以獲取到當前移動迭代器底層所使用的基礎疊代器。

舉個例子:
#include <iostream>
#include <vector>
#include <list>
#include <string>
using namespace std;
int main()
{
    typedef std::vector<std::string>::iterator Iter;

    //建立並初始化一個 vector 容器
    vector<std::string> myvec{ "STL","Java","Python" };
    //建立 2 個移動迭代器
    std::move_iterator<Iter>begin = make_move_iterator(myvec.begin());
    std::move_iterator<Iter>end = make_move_iterator(myvec.end());
    //以複製的方式初始化 othvec 容器
    vector <std::string> othvec(begin.base(), end.base());
   
    cout << "myvec:" << endl;
    //輸出 myvec 容器中的元素
    for (auto ch : myvec) {
        cout << ch << " ";
    }
    cout << endl << "othvec:" << endl;
    //輸出 othvec 容器中的元素
    for (auto ch : othvec) {
        cout << ch << " ";
    }
    return 0;
}
程式執行結果為:

myvec:
STL Java Python
othvec:
STL Java Python

顯然,通過呼叫 base() 成員方法,初始化 othvec 容器的方式轉變為以複製而非移動的方式,因此 myvec 容器不會受到影響。