C++ Vector swap操作前後迭代器為何不失效

飛信天下發表於2013-12-01

1 swap操作

swap是STL泛型操作的一種。這種操作的時間複雜度極低,用於兩個容器內容的交換。

例如定義vector vi1和vi2, vi1.swap(vi2),就將vi1和vi2的內容交換了。


2  問題


iterator實際上是一種指標,可以指向容器的任意位置。例如vector::iterator it1 = vi1.begin();

這兩個操作本身很簡單,但是怪異的一點是swap前後,迭代器不失效,原來指向什麼內容,swap後還是指向什麼內容。

例如下面這段程式碼:

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v1(10,1);
    vector<int> v2(100,2);
    vector<int>::iterator it1 = v1.begin();
    vector<int>::iterator it2 = v2.begin();
    cout<< "&v1[0]=" << &v1[0] << "\t\t" << "*it1=" <<*it1 << endl;
    cout<< "&v2[0]=" << &v2[0] << "\t\t" << "*it1=" <<*it2 << endl;
	
    v1.swap(v2);
    cout<<"after v1.swap(v2)"<<endl;
    cout<< "&v1[0]=" << &v1[0] << "\t\t" << "*it1=" <<*it1 << endl;
    cout<< "&v2[0]=" << &v2[0] << "\t\t" << "*it1=" <<*it2 << endl;

	return 0;
}   
結果如下:

結果表明:

(1)swap前後,*vt1和*vt2沒有改變,但是&v1[0]和&v2[0]卻發生了改變。

(2)實際上,交換操作的過程是這樣的:

    類物件地址不變,但是begin()操作指向的首地址改變,&v1[0]等效於begin操作,也改變。

    而迭代器指向的是“地址”,沒有改變,所以會產生這樣的效果。


下面這一段詳細的程式可以驗證:

#include <iostream>
#include <vector>
using namespace std;

int main()
{

	vector<int> v1(10,1);
    vector<int> v2(100,2);
    vector<int>::iterator it1 = v1.begin();
    vector<int>::iterator it2 = v2.begin();
    cout<<"v1的屬性:"<<endl;
    cout<< "&v1=" << &v1 << "\n" 
	<< "&v1[0]=" << &v1[0] << "\t\t" << "v1[0]=" << v1[0] << "\n"
	<< "it1="    << it1    << "\t\t" << "*it1="  << *it1  << "\n"
	<<"v1.begin()="<<v1.begin()  << "\t" << "*v1.begin()="<<*v1.begin()
	<< endl;

    cout<<"v2的屬性:"<<endl;
    cout<< "&v2=" << &v2 << "\n" 
	<< "&v2[0]=" << &v2[0] << "\t\t" << "v2[0]=" <<	v2[0]  << "\n"
	<< "it2="    << it2    << "\t\t" << "*it2="  << *it2   << "\n"
	<<"v2.begin()="<<v2.begin()  << "\t" << "*v2.begin()="<<*v2.begin()
	<< endl;
	
    v1.swap(v2);
	
    cout<<"---------after v1.swap(v2)----------"<<endl;
    cout<<"v1的屬性:"<<endl;
    cout<< "&v1=" << &v1 << "\n" 
	<< "&v1[0]=" << &v1[0] << "\t\t" << "v1[0]=" << v1[0] << "\n"
	<< "it1="    << it1    << "\t\t" << "*it1="  << *it1  << "\n"
	<<"v1.begin()="<<v1.begin()  << "\t" << "*v1.begin()="<<*v1.begin()
	<< endl;
	
    cout<<"v2的屬性:"<<endl;
    cout<< "&v2=" << &v2 << "\n" 
	<< "&v2[0]=" << &v2[0] << "\t\t" << "v2[0]=" << v2[0]  << "\n"
	<< "it2="    << it2    << "\t\t" << "*it2="  << *it2   << "\n"
	<<"v2.begin()="<<v2.begin()  << "\t" << "*v2.begin()="<<*v2.begin()
	<< endl;
    return 0;
}
結果如下:

結果表明:

(1)這裡&v1和&v1[0]不是同一個地址,前者是物件地址,後者是第一個元素地址。和陣列是不同的。

(2)swap前後,確實只有begin的地址被互換了。

(3)只互換begin,整個vector內容可以互換嗎?答案是肯定的,vector的訪問是基於首地址+偏移的,類似於陣列。

    所以如果將首地址互換,vector訪問到的所有元素都互換了。

記憶體示意圖:

3 原理

3.1 vector類

vector內部是維護一個動態陣列,大概樣子是這樣:

class vector

{

T* begin;

T* finish;

T* capacity;

};

vector::iterator雖說不一定是用指標實現,但是一定儲存了陣列中元素的真實記憶體地址

其實也不是一定,實現也可以儲存begin和偏移量,但這麼做明顯太複雜,實際中都是直接儲存元素地址的)。

vector使用swap的時候,其實交換的是雙方begin, finish, capacity這三個指標的值。

在這個過程中,所有從該vector建立的iterator本身都不會發生任何改變(打個比方就是你換一個手機號,那些存了你以前手機號的人手頭的電話號碼並不會隨之發生改變,除非你告訴他們)。

因為iterator沒有改變,所以你列印iterator本身的地址時,得到的仍舊是原來的值。

另外,動態陣列本身並沒有被銷燬,只是改變了所屬。

所以實際兩個vector中所有元素的實體地址都沒有改變,iterator指向的依然是原來的元素。

3.2 swap函式

下面這一段程式碼摘抄自stl_vector.h,我們主要關注begin和swap操作。


/**

   *  Returns a read/write iterator that points to the first

   *  element in the %vector.  Iteration is done in ordinary

   *  element order.

   */

  iterator

  begin() _GLIBCXX_NOEXCEPT

  { return iterator(this->_M_impl._M_start); }  


可見begin操作是獲取_M_impl._M_start這個值。


void _M_swap_data(_Vector_impl& __x)

{

  std::swap(_M_start, __x._M_start);

  std::swap(_M_finish, __x._M_finish);

  std::swap(_M_end_of_storage, __x._M_end_of_storage);

}  


swap操作也是對Mstart這個值進行處理。

3.3 模擬模擬

下面是一種常見的迭代器實現方案的簡化程式碼:

template <typename T>

struct base_iterator

{

base_iterator(T* p):m_ptr(p) {}

T* operator->() { return m_ptr; }

// 剩下的若干過載運算子。。。

T* m_ptr;

};


template <typename T>

class vector

{

typedef base_iterator<T> iterator;

T* begin;

T* finish;

T* capacity;


iterator begin() { return iterator(begin); }

};

獲取begin()迭代器實際上就是用begin指標建立了一個iterator物件而已


4 參考文獻

[1]http://www.software8.co/wzjs/cpp/5047.html          作者:henrystark

[2]http://bbs.csdn.net/topics/390410638?page=1               作者:cnm_1314 和 tofu_

相關文章