不要輕易定義指向std::vector中的元素的指標

發表於2023-11-26

類應該是被封裝的,類的使用者透過介面使用類提供的功能,而不必關心類的內部如何實現。然而,C++標準庫容器 std::vector 的實現滲透到了介面中來。對於以下程式碼:

    const int pushNum = 10;
    std::vector<int> v = { 1,2,3 };
    int* p = &v[1];
    std::cout << "*p = " << * p << std::endl;
    std::cout << "v[1] = " << v[1] << std::endl;
for (int i = 0; i < pushNum; i++) v.push_back(i); std::cout << "---------------------" << std::endl;
std::cout
<< "*p = " << *p << std::endl; std::cout << "v[1] = " << v[1] << std::endl;

我們初始化了一個有3個int元素的vector,定義了一個int 指標p,指向v[1] , 列印 *p 以及v[1] 的值。 然後向 v 中push_back() 了10 個元素,之後再次列印 *p 以及 v[1] 的值。在作者的環境中,程式的執行結果如下:

 

 可以看到,在最初的v 中,列印出的*p 以及 v[1] 的值都是2, 這正是容器中下標為 1 的元素的值。 而push_back() 一些元素之後,v[1] 仍然是2,但是*p 的值卻變了。指標p指向的就是v[1] ,為什麼會這樣呢?原因要追溯到標準庫std::vector的實現。

std::vector 物件動態管理記憶體空間,一個std::vector 物件所分配的所有記憶體空間未必都構造了元素。可以使用其成員函式size() 返回其已構造的元素數量, 使用capacity() 返回其已分配的總容量,即,已分配的記憶體一共可以構造多少個元素。隨著我們不斷push_back(),size 會逐漸增加,直到capacity沒有足夠的空間能夠容下下一個元素,這時,std::vector就會分配更多的記憶體。然而,std::vector 要求元素是連續儲存的,如果此時連續的記憶體已經沒有更多的空間了,std::vector會把所有元素搬到一塊其他的,更大的記憶體空間,來容納更多元素,並且其內部實現會保證其過載的下標訪問運算子 [ ] 是有效的,然而std::vector 無法得知此時有一個指標p指向了容器內的元素,所以無法更新指標,而指標所指向的舊的記憶體現在是未知的。

那麼std::vector 每次重新分配的時候會申請多大的空間作為capacity() ? 作者在自己的環境中做了一些實驗,但並沒有發現什麼規律。