簡體   English   中英

C ++ STL:哪種迭代方法比STL容器更好?

[英]C++ STL: Which method of iteration over a STL container is better?

這對你們中的一些人來說可能看起來很無聊,但是對於STL容器,以下哪兩種迭代方法更好? 為什么

class Elem;
typedef vector<Elem> ElemVec;
ElemVec elemVec;

// Method 0
for (ElemVec::iterator i = elemVec.begin(); i != elemVec.end(); ++i)
{
    Elem& e = *i;
    // Do something
}

// Method 1
for (int i = 0; i < elemVec.size(); ++i)
{
    Elem& e = elemVec.at(i);
    // Do something
}

方法0似乎更清晰STL,但方法1使用較少的代碼實現相同。 在容器上簡單的迭代是什么似乎遍布任何源代碼的地方。 所以,我傾向於選擇方法1,這似乎可以減少視覺混亂和代碼大小。

PS:我知道迭代器可以做的不僅僅是一個簡單的索引。 但是,請將回復/討論集中在如上所示的容器上的簡單迭代上。

第一個版本適用於任何容器,因此在將任何容器作為參數的模板函數中更有用。 即使對於矢量,它也可以略微提高效率。

第二個版本僅適用於矢量和其他整數索引容器。 對於那些容器來說,它更加慣用,C ++的新手很容易理解它,如果你需要對索引執行其他操作,這很有用,這種情況並不少見。

像往常一樣,我擔心沒有簡單的“這個更好”的答案。

如果你不介意(非常?)小的效率損失,我建議使用Boost.Foreach

BOOST_FOREACH( Elem& e, elemVec )
{
    // Your code
}

方法0更快,因此建議使用。

方法1使用size(),允許為O(1),具體取決於容器和stl實現。

以下迭代標准庫容器的方法是最好的。

使用 (及以后)的范圍內,基於for -loopauto

// Method 2
for (auto& e: elemVec)
{
    // Do something with e...
}

這與您的Method 0類似,但需要更少的輸入,更少的維護,並適用於與std::begin()std::end()兼容的任何容器, 包括普通舊數組。

方法0的一些更多優點:

  • 如果你從vector移動到另一個容器,循環保持不變,
  • 如果需要,很容易從迭代器移動到const_iterator,
  • 當c ++ 0x到來時,自動打字會減少一些代碼的混亂。

主要的缺點是,在許多情況下,您掃描兩個容器,在這種情況下,索引比保留兩個迭代器更清晰。

巧合的是我最近進行了速度測試(用rand()填充10 * 1024 * 1024英寸)。
這些是3次運行,時間以納秒為單位

vect[i] time      : 373611869  
vec.at(i) time    : 473297793  
*it = time        : 446818590  
arr[i] time       : 390357294  
*ptr time         : 356895778  

更新:添加了stl-algorithm std :: generate,由於特殊的迭代器優化(VC ++ 2008),它似乎運行速度最快。 微秒的時間。

vect[i] time      : 393951
vec.at(i) time    : 551387
*it = time        : 596080
generate = time   : 346591
arr[i] time       : 375432
*ptr time         : 334612

結論:使用標准算法,它們可能比顯式循環更快! (也是好的做法)

更新:以上時間處於I / O限制的情況下,我使用CPU綁定進行相同的測試(迭代一個相對較短的向量,它應該重復適合緩存,將每個元素乘以2並寫回向量)

//Visual Studio 2008 Express Edition
vect[i] time      : 1356811
vec.at(i) time    : 7760148
*it = time        : 4913112
for_each = time   : 455713
arr[i] time       : 446280
*ptr time         : 429595

//GCC
vect[i] time      : 431039
vec.at(i) time    : 2421283
*it = time        : 381400
for_each = time   : 380972
arr[i] time       : 363563
*ptr time         : 365971  

有趣的是,與for_each相比,VC ++中的迭代器和operator []相當慢(這似乎會降低迭代器的性能,通過一些模板魔法降低指針的性能)。
在GCC中,訪問時間僅對at()更糟,這是正常的,因為它是測試中唯一的范圍檢查函數。

方法0,原因有幾個。

  • 它更好地表達了您的意圖,這有助於編譯器優化和可讀性
  • 逐個錯誤的可能性較小
  • 即使你用一個沒有operator []的列表替換向量也可以工作

當然,最好的解決方案通常是解決方案2:std算法之一。 std :: for_each,std :: transform,std :: copy或其他你需要的東西。 這有一些進一步的優點:

  • 它更好地表達了您的意圖,並允許一些重要的編譯器優化(MS的安全SCL對您的方法0和1執行邊界檢查,但將在std算法上跳過它)
  • 它的代碼較少(至少在調用站點。當然你必須編寫一個函子或者某些東西來替換循環體,但是在使用站點,代碼會被清理掉很多,這可能是它最重要的地方。

通常,避免過度指定代碼。 准確指定您想要完成的任務,而不是其他內容。 std算法通常是去那里的方法,但即使沒有它們,如果你不需要索引i ,為什么要這樣做呢? 在這種情況下,請使用迭代器。

這取決於哪種類型的容器。 對於vector ,使用哪個可能無關緊要。 方法0已經變得更加慣用,但正如大家所說,它們並沒有太大的區別。

如果你決定使用一個list ,相反,方法1原則上是O(N) ,但事實上at()方法中沒有列表,所以你甚至不能這樣做。 (所以在某種程度上你的問題疊加在甲板上。)

但這本身就是方法0的另一個參數:它對不同的容器使用相同的語法。

上面沒有考慮的可能性:取決於“做某事”的細節,可以同時使用方法0和方法1,您不必選擇:

for (auto i = elemVec.begin(), ii = 0; ii < elemVec.size(); ++i, ++ii)
{
    // Do something with either the iterator i or the index ii
}

這樣,找到索引或訪問相應的成員都是以微不足道的復雜性獲得的。

暫無
暫無

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

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