簡體   English   中英

移動向量會使迭代器無效嗎?

[英]Does moving a vector invalidate iterators?

如果我在向量a有一個迭代器,那么我從a移動構造或移動分配向量b ,那么該迭代器是否仍指向同一元素(現在在向量b )? 這就是我在代碼中的意思:

#include <vector>
#include <iostream>

int main(int argc, char *argv[])
{
    std::vector<int>::iterator a_iter;
    std::vector<int> b;
    {
        std::vector<int> a{1, 2, 3, 4, 5};
        a_iter = a.begin() + 2;
        b = std::move(a);
    }
    std::cout << *a_iter << std::endl; // Is a_iter valid here?
    return 0;
}

由於a已移至ba_iter仍然有效,還是該迭代a_iter迭代器無效? 作為參考, std::vector::swap 不會使迭代器無效

盡管可以合理地假設在move之后iterator仍然有效,但我認為Standard並不能真正保證這一點。 因此,在move之后,迭代器處於未定義狀態。


沒有引用我可以在其中明確指出 ,之前已經存在迭代器的標准找到move仍然是有效的 move

從表面上看,假設iterator 通常被實現為指向受控序列的指針,這似乎是完全合理的。 如果是這種情況,那么在move之后,迭代器仍然有效。

但是iterator的實現是實現定義的。 意思是,只要特定平台上的iterator滿足標准規定的要求,就可以以任何方式實施。 從理論上講,它可以實現為返回vector類的指針和索引的組合。 如果真是這樣,那么在move之后,迭代器將變得無效。

iterator是否實際以這種方式實現是無關緊要的。 可以通過這種方式來實現,因此,如果標准中沒有明確保證move后迭代器仍然有效,則不能假設它們是有效的。 還請記住, swap之后,迭代器具有這樣的保證。 這是從先前的標准中明確闡明的。 也許這只不過是標准comittee不要做出類似的澄清后,迭代器的監督move ,但在任何情況下,有沒有這樣的保證。

因此,總而言之,你不能認為move之后迭代器仍然是好的。

編輯:

第n3242號草案中的23.2.1 / 11指出:

除非另有說明(顯式指定或通過在其他函數中定義一個函數),否則調用容器成員函數或將容器作為參數傳遞給庫函數均不得使對該容器內對象的迭代器或更改其值無效。

這可能導致人們得出以下結論:迭代器在move之后是有效的,但我不同意。 在您的示例代碼中, a_itervector a的迭代器。 在之后move ,該容器, a肯定已經改變了。 我的結論是以上條款不適用於這種情況。

我認為將移動構造更改為移動分配的編輯會更改答案。

至少如果我正確地讀取了表96,則移動構造的復雜性將以“ note B”的形式給出,這對於除std::array以外的任何事物都是恆定的。 但是,移動分配的復雜度是線性的。

這樣,移動構造基本上別無選擇,只能從源復制指針,在這種情況下,很難看到迭代器如何變得無效。

但是,對於移動分配,線性復雜度意味着它可以選擇將單個元素從源移動到目的地,在這種情況下,迭代器幾乎肯定會變得無效。

元素移動分配的可能性通過以下描述得到了增強:“ a的所有現有元素都被分配或銷毀了”。 “銷毀”部分將對應於銷毀現有內容,並從源頭“竊取”指針,但是“移動到”將指示將各個元素從源頭移到目的地。

由於沒有什么可以阻止迭代器保留對原始容器的引用或指針,因此除非您在標准中找到明確的保證,否則您不能依靠迭代器保持有效。

tl; dr:是的,移動std::vector<T, A>可能會使迭代器無效

常見情況(使用std::allocator的情況)是不會發生無效,但不能保證,如果您依賴於當前實現未實現的事實,則切換編譯器甚至是下一個編譯器更新可能會使您的代碼行為不正確。使迭代器無效。


移動作業

在將移動分配與向量模板的分配器意識相關聯之后, std::vector迭代器是否可以實際上保持有效的問題取決於分配器類型(可能還有其各自的實例)。

在我所看到的每個實現中, std::vector<T, std::allocator<T>> 1的移動分配實際上不會使迭代器或指針無效。 但是,有一個問題,當歸結為使用它時,因為該標准通常不能保證迭代器對於std::vector實例的任何移動分配通常都是有效的,因為該容器可以識別分配器。

自定義分配器可能具有狀態,並且如果它們在移動分配時不傳播並且比較不相等,則向量必須使用其自己的分配器為移動的元素分配存儲。

讓:

std::vector<T, A> a{/*...*/};
std::vector<T, A> b;
b = std::move(a);

現在如果

  1. std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
  2. std::allocator_traits<A>::is_always_equal::value == false &&可能從c ++ 17開始
  3. a.get_allocator() != b.get_allocator()

然后b將分配新的存儲並將a元素一一移動到該存儲中,從而使所有迭代器,指針和引用無效。

原因是滿足以上條件1.禁止在容器移動時分配分配器的移動。 因此,我們必須處理兩個不同的分配器實例。 如果這兩個分配器對象現在既不總是比較等於( 2 ),也不實際等於相等,則兩個分配器的狀態都不同。 分配器x可能無法解除分配狀態不同的另一個分配器y內存,因此具有分配器x的容器不能僅從通過y分配了其內存的容器中竊取內存。

如果移動分配的分配器傳播或者如果兩個分配器比較相等,那么實施將很有可能選擇只讓b自己a s的數據,因為它可以確保能夠正確地解除分配存儲。

1std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment std::allocator_traits<std::allocator<T>>::is_always_equal std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignmentstd::allocator_traits<std::allocator<T>>::is_always_equal都是std::true_type (對於任何非專用std::allocator )。


在施工中

std::vector<T, A> a{/*...*/};
std::vector<T, A> b(std::move(a));

知道分配器的容器的move構造函數將從當前表達式要從其移動的容器的分配器實例移動構造其分配器實例。 因此,可以確保適當的釋放能力,並且可以(實際上將)竊取內存,因為移動構造(除std::array )必然具有恆定的復雜性。

注意:即使對於move構造,仍然不能保證迭代器保持有效。


交換時

要求兩個向量的迭代器在交換后保持有效(現在僅指向相應的交換容器)很容易,因為交換僅在以下情況下具有已定義的行為:

  1. std::allocator_traits<A>::propagate_on_container_swap::value == true ||
  2. a.get_allocator() == b.get_allocator()

因此,如果分配器在交換時不傳播並且比較不相等,則交換容器首先是未定義的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM