ข้อดีของ std :: for_each สำหรับลูป


168

มีข้อดีของการใด ๆstd::for_eachมากกว่าforห่วง? สำหรับฉันstd::for_eachดูเหมือนว่าจะเป็นอุปสรรคต่อการอ่านรหัสเท่านั้น ทำไมมาตรฐานการเข้ารหัสบางอย่างแนะนำให้ใช้?


10
std::for_eachเมื่อใช้กับboost.lambdaหรือboost.bindสามารถปรับปรุงความสามารถในการอ่านได้บ่อยครั้ง

คำถามและคำตอบที่ได้รับการยอมรับนั้นมาจาก 2010 สำหรับคำตอบ uptodate เพิ่มเติม (จากปี 2018) ดูที่นี่: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-Halevi

คำตอบ:


181

สิ่งที่ดีกับC ++ 11 (ก่อนหน้านี้เรียกว่า C ++ 0x) คือการอภิปรายที่น่าเบื่อนี้จะถูกตัดสิน

ฉันหมายความว่าไม่มีใครในใจที่ถูกต้องของพวกเขาที่ต้องการจะย้ำกับคอลเลกชันทั้งหมดจะยังคงใช้มัน

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
   foo(*it);
}

หรือสิ่งนี้

for_each(collection.begin(), collection.end(), [](Element& e)
{
   foo(e);
});

เมื่อไวยากรณ์วนลูปตามช่วงforพร้อมใช้งาน:

for(Element& e : collection)
{
   foo(e);
}

ไวยากรณ์ชนิดนี้มีอยู่ใน Java และ C # ในบางเวลาและที่จริงมีforeachลูปมากกว่าforลูปแบบคลาสสิกในโค้ด Java หรือ C # ทุกครั้งที่ฉันเห็น


12
ที่จริงแล้ววง foreach กับ scoop นั้นมีมานานแล้ว แต่ฉันก็ยังอยากจะทำซ้ำกับ for_each และฟังก์ชั่นแลมบ์ดา
Viktor Sehr

19
สมมติฐานเกี่ยวกับการต้องการช่วงที่เก็บทั้งหมดไม่ได้เป็นส่วนหนึ่งของคำถามดังนั้นนี่เป็นเพียงคำตอบบางส่วน
Adrian McCarthy

6
โปรดทราบว่าการวนซ้ำองค์ประกอบอาจไม่ใช่สิ่งเดียวที่คุณต้องการดังนั้นจึงควรใช้ for_each เพื่อให้คุณเรียนรู้เกี่ยวกับ find / partition / copy_replace_if และอื่น ๆ ซึ่งเป็นสิ่งที่ลูปจริง ๆ ทำ.
Macke

10
พิสัยสำหรับดียกเว้นเมื่อคุณต้องการตัววนซ้ำจริง ๆ (จากนั้นก็ไม่มีทางไปถึงได้)
Damon

5
ฉันจะไม่ใช้แม้Element & eในขณะที่ auto & e(หรือauto const &e) ดูดีขึ้น ฉันต้องการใช้Element const e(โดยไม่มีการอ้างอิง) Elementเมื่อฉันต้องการแปลงนัยกล่าวว่าเมื่อมาเป็นคอลเลกชันของประเภทที่แตกต่างกันและฉันต้องการให้พวกเขาแปลงเป็น
นาวาซ

54

นี่คือสาเหตุบางประการ:

  1. ดูเหมือนว่าจะเป็นอุปสรรคต่อการอ่านเพียงเพราะคุณไม่คุ้นเคยกับมันและ / หรือไม่ได้ใช้เครื่องมือที่เหมาะสมเพื่อทำให้มันง่าย (ดูที่บูสเตอร์ :: range :: boost และ :: :: bind / boost :: lambda สำหรับผู้ช่วยเหลือเหล่านี้จะเป็น C ++ 0x และทำให้ for_each และฟังก์ชั่นที่เกี่ยวข้องมีประโยชน์มากกว่า)

  2. อนุญาตให้คุณเขียนอัลกอริทึมที่ด้านบนของ for_each ที่ทำงานร่วมกับตัววนซ้ำใดก็ได้

  3. มันลดโอกาสของการพิมพ์บักโง่

  4. นอกจากนี้ยังเปิดใจของคุณส่วนที่เหลือของ STL-ขั้นตอนวิธีการเช่นfind_if, sort, replaceฯลฯ และเหล่านี้จะไม่ดูแปลกอีกต่อไปดังนั้น นี่อาจเป็นชัยชนะครั้งใหญ่

อัปเดต 1:

ที่สำคัญที่สุดคือช่วยให้คุณก้าวไปข้างหน้าfor_eachเทียบกับ for-loops อย่างที่มีอยู่ทั้งหมดและดู STL-alogs อื่น ๆ เช่น find / sort / partition / copy_replace_if, การประมวลผลแบบขนาน .. หรืออะไรก็ตาม

การประมวลผลจำนวนมากสามารถเขียนได้อย่างกระชับโดยใช้ "ส่วนที่เหลือ" ของพี่น้องของ for_each แต่ถ้าสิ่งที่คุณทำคือการเขียน for-loop ด้วยตรรกะภายในที่หลากหลายคุณจะไม่มีทางเรียนรู้วิธีใช้สิ่งเหล่านั้น จบลงด้วยการคิดค้นล้อซ้ำแล้วซ้ำอีก

และ (สำหรับช่วงสไตล์เร็ว ๆ นี้สำหรับ for_each):

for_each(monsters, boost::mem_fn(&Monster::think));

หรือกับ lambdas C ++ x11:

for_each(monsters, [](Monster& m) { m.think(); });

IMO สามารถอ่านได้มากกว่า:

for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
    i->think();
} 

เช่นนี้ (หรือกับ lambdas, เห็นคนอื่น ๆ ):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

กระชับกว่า:

for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
    my_monkey->eat(*i);
} 

โดยเฉพาะอย่างยิ่งถ้าคุณมีฟังก์ชั่นการโทรที่หลากหลายตามลำดับ ... แต่บางทีนั่นอาจเป็นฉัน ;)

อัปเดต 2 : ฉันได้เขียน wraps หนึ่งซับของ stl-algos ที่ทำงานกับช่วงแทนตัววนซ้ำคู่หนึ่ง เพิ่ม :: range_ex เมื่อเปิดตัวจะรวมอยู่ในนั้นและอาจจะมีใน C ++ 0x ด้วยหรือไม่


+1, ฟังก์ชั่นหรือประเภทซ้อนกันหลาย: outer_class::inner_class::iteratorหรือพวกเขาเป็นข้อโต้แย้งแม่แบบ: typename std::vector<T>::iterator... สำหรับการสร้างตัวเองสามารถทำงานเป็นสายการสร้างจำนวนมากในตัวเอง
David Rodríguez - dribeas

4
(btw: for_eachตัวอย่างที่สองไม่ถูกต้อง (ควรfor_each( bananas.begin(), bananas.end(),...
David Rodríguez - dribeas

ฉันเขียนชุดคลุมที่ใช้ช่วงแทนตัววนซ้ำสองตัว สิ่งเหล่านี้จะพร้อมใช้งานในภายหลัง (ดู range_ex) แต่ทุกคนควรมีในภายหลัง (เพิ่มการอัปเดตในเรื่องนั้น)
Macke

1
การสนับสนุนการประมวลผลแบบขนานคือไม่ 1 เหตุผลที่นี่ เราสามารถเพิ่มการนำไปใช้เพื่อใช้ cuda / gpu สำหรับการคำนวณแบบต่างกัน
shuva

25

for_eachเป็นเรื่องทั่วไปมากขึ้น คุณสามารถใช้มันเพื่อวนซ้ำคอนเทนเนอร์ทุกประเภท (โดยผ่านในตัววนเริ่มต้น / สิ้นสุด) คุณสามารถสลับคอนเทนเนอร์ใต้ฟังก์ชันที่ใช้for_eachโดยไม่ต้องอัปเดตรหัสซ้ำ คุณต้องพิจารณาว่ามีภาชนะอื่น ๆ ในโลกกว่าstd::vectorและธรรมดาอาร์เรย์ C for_eachเก่าที่จะเห็นประโยชน์ของ

ข้อเสียเปรียบหลักของfor_eachมันคือการใช้ functor ดังนั้นไวยากรณ์เป็น clunky สิ่งนี้ได้รับการแก้ไขใน C ++ 11 (เดิมคือ C ++ 0x) โดยมีการแนะนำ lambdas:

std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
    i+= 10;
});

นี้จะไม่ดูแปลกสำหรับคุณใน 3 ปี


3
+1 โฆษณาเมื่อ for_each ใช้คอนเทนเนอร์ / พิสัยแทนตัววนซ้ำสองตัวมันยอดเยี่ยมกว่าเดิมมาก
Macke

2
@Marcus: นั่นจะเป็นช่วงสำหรับการสร้างและไวยากรณ์จะไม่อ่าน 'for_each' ในตัวเอง: for ( int v : int_vector ) {(แม้ว่าจะสามารถจำลองวันนี้ด้วย BOOST_FOREACH)
David Rodríguez - dribeas

@ David: ผมหมายถึงการเพิ่มฟังก์ชั่นทั่วไปของช่วง-based (เพื่อให้คุณสามารถใช้กับทุกช่วง for_each เหล่านี้คัดลอกฟังก์ชั่น remove_if ฯลฯ ฯลฯ )
Macke

1
ทำไมจึงไม่สามารถเขียน: std::for_each(container, [](int& i){ ... });. ฉันหมายถึงเหตุใดจึงถูกบังคับให้เขียนคอนเทนเนอร์สองครั้ง
Giorgio

1
@freitass: การเขียนคอนเทนเนอร์หนึ่งครั้งในความคิดเห็นก่อนหน้าของฉันอาจเป็นค่าเริ่มต้นที่จะใช้ iterator เริ่มต้นที่สิ้นสุดโดยไม่ต้องเรียกพวกเขาอย่างชัดเจน ภาษาส่วนใหญ่ที่มีฟังก์ชั่นการเรียงลำดับที่สูงกว่าในคอลเลกชัน (Ruby, Scala, ... ) เขียนสิ่งที่ชอบcontainer.each { ... }โดยไม่ต้องกล่าวถึงตัวเริ่มต้นและสิ้นสุด ฉันพบว่ามันซ้ำซ้อนเล็กน้อยที่ฉันต้องระบุตัววนซ้ำสุดท้ายตลอดเวลา
Giorgio

18

โดยส่วนตัวเมื่อใดก็ตามที่ฉันต้องออกไปใช้std::for_each(เขียนฟังก์ชั่นวัตถุประสงค์พิเศษ / ซับซ้อนboost::lambdas) ฉันจะหาBOOST_FOREACHและใช้ช่วง C + 0x เพื่อความชัดเจนมากขึ้น:

BOOST_FOREACH(Monster* m, monsters) {
     if (m->has_plan()) 
         m->act();
}

VS

std::for_each(monsters.begin(), monsters.end(), 
  if_then(bind(&Monster::has_plan, _1), 
    bind(&Monster::act, _1)));

12

มันเป็นอัตนัยมากบางคนบอกว่าการใช้for_each จะทำให้โค้ดอ่านง่ายขึ้นเนื่องจากช่วยให้สามารถปฏิบัติต่อคอลเลกชันที่แตกต่างกันด้วยแบบแผนเดียวกัน for_eachitslef มีการใช้งานเป็นวง

template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function f)
  {
    for ( ; first!=last; ++first ) f(*first);
    return f;
  }

ดังนั้นขึ้นอยู่กับคุณที่จะเลือกสิ่งที่ถูกต้องสำหรับคุณ


11

เช่นเดียวกับฟังก์ชั่นอัลกอริธึมปฏิกิริยาแรกคือการคิดว่าไม่สามารถอ่าน foreach ได้มากกว่าการวนซ้ำ มันเป็นหัวข้อของสงครามเพลิงหลายครั้ง

เมื่อคุณคุ้นเคยกับสำนวนแล้วคุณอาจพบว่ามีประโยชน์ ข้อดีอย่างหนึ่งที่เห็นได้ชัดคือมันบังคับให้ coder แยกเนื้อหาด้านในของลูปออกจากฟังก์ชันการทำซ้ำตามจริง (ตกลงฉันคิดว่ามันเป็นข้อได้เปรียบอื่น ๆ บอกว่าคุณเพิ่งสับโค้ดโดยไม่มีผลประโยชน์จริง)

ข้อดีอีกอย่างหนึ่งคือเมื่อเห็น foreach ฉันรู้ว่าทุกรายการจะถูกประมวลผลหรือมีข้อยกเว้นจะถูกโยน

สำหรับห่วงช่วยให้หลายตัวเลือกสำหรับการยกเลิกห่วง คุณสามารถปล่อยให้วงวนทำงานเต็มหลักสูตรหรือคุณสามารถใช้คีย์เวิร์ดbreakเพื่อกระโดดออกจากลูปอย่างชัดเจนหรือใช้คีย์เวิร์ดreturnเพื่อออกจากฟังก์ชันมิดลูปทั้งหมด ในทางตรงกันข้ามforeachไม่อนุญาตให้ใช้ตัวเลือกเหล่านี้และทำให้อ่านได้ง่ายขึ้น คุณสามารถมองไปที่ชื่อฟังก์ชั่นและคุณก็รู้ถึงลักษณะของการวนซ้ำ

นี่คือตัวอย่างของความสับสนสำหรับลูป:

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
   /////////////////////////////////////////////////////////////////////
   // Imagine a page of code here by programmers who don't refactor
   ///////////////////////////////////////////////////////////////////////
   if(widget->Cost < calculatedAmountSofar)
   {
        break;
   }
   ////////////////////////////////////////////////////////////////////////
   // And then some more code added by a stressed out juniour developer
   // *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
   /////////////////////////////////////////////////////////////////////////
   for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
   {
      if(ip->IsBroken())
      {
         return false;
      }
   }
}

1
คุณสร้างจุดที่ดี แต่ตัวอย่างแรงจูงใจของคุณไม่ยุติธรรมเลย เมื่อคุณใช้std::for_each()ในมาตรฐานเก่า (ในช่วงเวลาของโพสต์นี้) คุณจะต้องใช้ functor ที่มีชื่อซึ่งจะส่งเสริมให้อ่านง่ายในขณะที่คุณพูดและห้ามการแตกออกจากวงก่อนกำหนด แต่จากนั้นforลูปที่เทียบเท่าจะไม่มีอะไรเลยนอกจากการเรียกใช้ฟังก์ชั่น แต่นอกเหนือจากนั้นฉันคิดว่าคุณสร้างจุดที่ยอดเยี่ยมในการบอกว่าstd::for_each() บังคับให้ผ่านช่วงทั้งหมด
wilhelmtell

10

คุณถูกต้องส่วนใหญ่แล้วส่วนใหญ่แล้วstd::for_eachเป็นผลขาดทุนสุทธิ ฉันต้องการไปให้ไกลที่สุดเท่าที่จะเปรียบเทียบไปfor_each มอบการควบคุมการไหลที่หลากหลายที่สุดเท่าที่จะเป็นไปได้ - คุณสามารถใช้มันเพื่อสร้างโครงสร้างการควบคุมอื่น ๆ ที่คุณสามารถจินตนาการได้ อย่างไรก็ตามความสามารถรอบด้านนั้นหมายถึงการมองเห็นสิ่งที่แยกจากกันไม่ได้บอกอะไรคุณเกี่ยวกับสิ่งที่ตั้งใจทำในสถานการณ์นี้ เป็นผลให้ไม่มีใครในใจที่ถูกต้องใช้ยกเว้นเป็นวิธีสุดท้ายgotogotogotogoto

ในบรรดาอัลกอริธึมมาตรฐานนั้นfor_eachก็เป็นวิธีเดียวกัน - สามารถใช้เพื่อนำไปใช้งานได้จริงซึ่งหมายความว่าการเห็นfor_eachจะไม่บอกอะไรเกี่ยวกับสิ่งที่มันถูกใช้ในสถานการณ์นี้ น่าเสียดายที่ทัศนคติของผู้คนต่อfor_eachเรื่องที่ว่าทัศนคติของพวกเขามีต่อgotoในปี 1970 หรือมากกว่านั้นมีไม่กี่คนที่ติดอยู่กับความจริงที่ว่ามันควรจะใช้เป็นทางเลือกสุดท้ายเท่านั้น แต่หลายคนยังคงคิดว่ามันเป็น ไม่ค่อยมีใครใช้เลย เวลาส่วนใหญ่แม้จะมองอย่างรวดเร็วก็จะแสดงให้เห็นว่าหนึ่งในทางเลือกนั้นเหนือกว่าอย่างมาก

for_eachเพียงแค่ยกตัวอย่างเช่นผมค่อนข้างมั่นใจว่าฉันเสียติดตามกี่ครั้งที่ผมเคยเห็นคนเขียนโค้ดที่จะพิมพ์ออกเนื้อหาของคอลเลกชันโดยใช้ for_eachขึ้นอยู่กับการโพสต์ที่ผมเคยเห็นนี้อาจจะดีการใช้งานทั่วไปมากที่สุดคนหนึ่งของ พวกเขาจบลงด้วยสิ่งที่ชอบ:

class XXX { 
// ...
public:
     std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

และการโพสต์ของพวกเขาจะถามเกี่ยวกับสิ่งที่รวมกันของbind1st, mem_funฯลฯ พวกเขาต้องการที่จะทำให้สิ่งที่ชอบ:

std::vector<XXX> coll;

std::for_each(coll.begin(), coll.end(), XXX::print);

collทำงานและพิมพ์ออกมาองค์ประกอบของ ถ้ามันใช้งานได้จริงอย่างที่ฉันเขียนไว้ตรงนั้นมันน่าจะปานกลาง แต่ก็ไม่ได้ - และเมื่อคุณได้มันมาทำงานมันยากที่จะหารหัสสองสามบิตที่เกี่ยวข้องกับสิ่งที่ เกิดขึ้นท่ามกลางชิ้นส่วนที่ถือมันไว้ด้วยกัน

โชคดีที่มีวิธีที่ดีกว่ามาก เพิ่มตัวแทรกตัวแทรกกระแสปกติสำหรับ XXX:

std::ostream &operator<<(std::ostream *os, XXX const &x) { 
   return x.print(os);
}

และใช้std::copy:

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));

ที่ทำงาน - และใช้เวลาจริงการทำงานที่ไม่ทั้งหมดที่จะคิดออกว่าจะพิมพ์เนื้อหาของการcollstd::cout


+1 มีข้อผิดพลาดเกิดขึ้น ในตัวอย่างแรกควรเป็นboost::mem_fn(&XXX::print)มากกว่าXXX::print
missingfaktor

นั่นเป็นเหตุผลที่ฉันบอกว่าตัวอย่างไม่ทำงานและพวกเขากำลังโพสต์ขอความช่วยเหลือเพื่อให้มันทำงาน (โอ้และคุณต้องผูกstd::coutเป็นอาร์กิวเมนต์เพื่อให้ทำงาน)
Jerry Coffin

1
แม้ว่าโดยทั่วไปแล้วจะเป็นคำตอบที่ดี แต่คำถามนั้นไม่เกี่ยวกับค่าของ for_each หรือค่าของมันเมื่อเทียบกับอัลกอริธึม std อื่น ๆ แต่เกี่ยวกับค่าของมันเทียบกับลูปสำหรับลูป คุณอาจคิดในกรณีที่ไม่มีอัลกอริทึม std อื่นที่ใช้ คุณจะใช้ for_each หรือ a for loop ได้ไหม คิดเกี่ยวกับมันและอะไรก็ตามที่คุณคิดขึ้นมานั่นควรจะเป็นคำตอบของคุณ
Christian Rau

@ChristianRau: มีเส้นแบ่งระหว่างการตอบคำถามตามที่ถามเสมอและพยายามให้ข้อมูลที่เป็นประโยชน์ คำตอบที่ตรงกับคำถามที่เขาถามอย่างตรงไปตรงมาคือ "อาจจะไม่ได้ใครจะไปรู้ล่ะ" แต่จะไร้ประโยชน์เกินกว่าที่จะคุ้มค่ากับความรำคาญ ในขณะเดียวกันการออกไปไกลเกินไป (เช่นการแนะนำ Haskell แทนที่จะเป็นข้อใดข้อหนึ่งข้างต้น) ก็ไม่น่าจะมีประโยชน์อะไรเช่นกัน
Jerry Coffin

3
@ChristianRau: คุณคิดได้อย่างไรว่า "... ส่วนใหญ่แล้ว std :: for_each คือการสูญเสียสุทธิ" ไม่ได้ตอบคำถามว่า std :: for_each ให้ประโยชน์อย่างไร
Jerry Coffin

8

ข้อดีของการเขียนฟังก์ชั่นสำหรับ beeing ที่อ่านได้มากขึ้นอาจไม่ปรากฏขึ้นเมื่อใดfor(...)และfor_each(...)

หากคุณใช้อัลกอริธึมทั้งหมดใน functional.h แทนที่จะใช้ for-ลูปรหัสจะอ่านง่ายขึ้นมาก

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);

เป็นมากเพิ่มเติมอ่านได้กว่า;

Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (*it > *longest_tree) {
     longest_tree = it;
   }
}

Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (it->type() == LEAF_TREE) {
     leaf_tree  = it;
     break;
   }
}

for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); 
     it != forest.end(); 
     it++, jt++) {
          *jt = boost::transformtowood(*it);
    }

for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
    std::makeplywood(*it);
}

และนั่นคือสิ่งที่ฉันคิดว่าเป็นสิ่งที่ดีมากพูดคุยกับ for-loops กับฟังก์ชันหนึ่งบรรทัด =)


6

ง่าย ๆ : for_eachมีประโยชน์เมื่อคุณมีฟังก์ชั่นในการจัดการทุกรายการอาเรย์ดังนั้นคุณไม่จำเป็นต้องเขียนแลมบ์ดา แน่นอนว่าสิ่งนี้

for_each(a.begin(), a.end(), a_item_handler);

ดีกว่า

for(auto& item: a) {
    a_item_handler(a);
}

นอกจากนี้การforวนซ้ำจะวนซ้ำวนซ้ำตั้งแต่ต้นจนจบเท่านั้นและfor_eachมีความยืดหยุ่นมากกว่า


4

การfor_eachวนซ้ำนั้นหมายถึงการซ่อนตัววนซ้ำ (รายละเอียดของการวนซ้ำ) จากรหัสผู้ใช้และกำหนดความหมายที่ชัดเจนเกี่ยวกับการดำเนินการ: แต่ละองค์ประกอบจะถูกวนซ้ำทันที

ปัญหาเกี่ยวกับความสามารถในการอ่านในมาตรฐานปัจจุบันคือต้องใช้ functor เป็นอาร์กิวเมนต์สุดท้ายแทนที่จะเป็นบล็อคของรหัสดังนั้นในหลายกรณีคุณต้องเขียนประเภท functor เฉพาะสำหรับมัน ทำให้กลายเป็นรหัสที่อ่านได้น้อยลงเนื่องจากไม่สามารถกำหนดวัตถุ functor ในสถานที่ (คลาสท้องถิ่นที่กำหนดภายในฟังก์ชั่นไม่สามารถใช้เป็นอาร์กิวเมนต์ของเทมเพลต) และการดำเนินการของลูปจะต้องย้ายออกจากลูปจริง

struct myfunctor {
   void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), myfunctor() );
   // more code
}

โปรดทราบว่าหากคุณต้องการดำเนินการเฉพาะกับแต่ละวัตถุคุณสามารถใช้std::mem_fnหรือboost::bind( std::bindในมาตรฐานถัดไป) หรือboost::lambda(lambdas ในมาตรฐานถัดไป) เพื่อให้ง่ายขึ้น:

void function( int value );
void apply( std::vector<X> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
   // code
}

ซึ่งไม่สามารถอ่านได้น้อยและกะทัดรัดกว่ารุ่นรีดมือถ้าคุณมีฟังก์ชั่น / วิธีการโทรเข้าที่ การใช้งานสามารถให้การใช้งานอื่น ๆ ของfor_eachวง (คิดว่าการประมวลผลแบบขนาน)

มาตรฐานที่กำลังจะมาถึงจะดูแลข้อบกพร่องบางอย่างในรูปแบบที่แตกต่างกันซึ่งจะอนุญาตให้คลาสที่กำหนดแบบโลคัลเป็นอาร์กิวเมนต์สำหรับเทมเพลต:

void apply( std::vector<int> const & v ) {
   // code
   struct myfunctor {
      void operator()( int ) { code }
   };
   std::for_each( v.begin(), v.end(), myfunctor() );
   // code
}

การปรับปรุงตำแหน่งของรหัส: เมื่อคุณเรียกดูคุณจะเห็นสิ่งที่กำลังทำอยู่ที่นั่น ตามความเป็นจริงคุณไม่จำเป็นต้องใช้ไวยากรณ์ของคลาสเพื่อกำหนด functor แต่ใช้แลมบ์ดาตรงนั้น:

void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), 
      []( int ) { // code } );
   // code
}

แม้ว่าในกรณีของfor_eachจะมีโครงสร้างเฉพาะที่จะทำให้เป็นธรรมชาติมากขึ้น:

void apply( std::vector<int> const & v ) {
   // code
   for ( int i : v ) {
      // code
   }
   // code
}

ฉันมักจะผสมสิ่งfor_eachก่อสร้างกับลูปที่รีดด้วยมือ เมื่อมีเพียงการเรียกไปยังฟังก์ชันหรือเมธอดที่มีอยู่คือสิ่งที่ฉันต้องการ ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )) ฉันจะไปที่สิ่งfor_eachก่อสร้างที่นำรหัสออกไปจากหม้อไอน้ำจำนวนมากจำนวนมาก เมื่อฉันต้องการบางสิ่งที่ซับซ้อนมากขึ้นและฉันไม่สามารถใช้ functor เพียงไม่กี่บรรทัดเหนือการใช้งานจริงฉันจะหมุนวนของตัวเอง ในส่วนที่ไม่สำคัญของรหัสฉันอาจไปกับ BOOST_FOREACH (เพื่อนร่วมงานพาฉันเข้าไป)


3

นอกเหนือจากความสามารถในการอ่านและประสิทธิภาพแล้วแง่มุมหนึ่งที่มักถูกมองข้ามก็คือความสม่ำเสมอ มีหลายวิธีในการใช้ลูป for (หรือ while) บนตัววนซ้ำจาก:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
    do_something(*iter);
}

ถึง:

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
    do_something(*iter);
    ++iter;
}

ด้วยตัวอย่างมากมายในระหว่างที่ระดับต่าง ๆ ของประสิทธิภาพและศักยภาพแมลง

อย่างไรก็ตามการใช้ for_each บังคับให้เกิดความสม่ำเสมอโดยการวนลูปออก:

for_each(c.begin(), c.end(), do_something);

สิ่งเดียวที่คุณต้องกังวลในตอนนี้คือ: คุณใช้ร่างกายวนเป็นฟังก์ชั่น, functor หรือแลมบ์ดาที่ใช้คุณสมบัติ Boost หรือ C ++ 0x? โดยส่วนตัวฉันค่อนข้างกังวลเกี่ยวกับเรื่องนั้นมากกว่าวิธีการนำไปใช้หรืออ่านแบบสุ่มสำหรับ / ในขณะที่วนซ้ำ


3

ฉันเคยเกลียดstd::for_eachและคิดว่าถ้าปราศจากแลมบ์ดามันทำผิดไปอย่างสิ้นเชิง อย่างไรก็ตามฉันได้เปลี่ยนใจเมื่อไม่นานมานี้และตอนนี้ฉันรักมันจริง ๆ และฉันคิดว่ามันปรับปรุงการอ่านได้ง่ายขึ้นและทำให้ง่ายขึ้นในการทดสอบรหัสของคุณในแบบ TDD

std::for_eachอัลกอริทึมสามารถอ่านได้บางสิ่งบางอย่างที่ต้องทำกับองค์ประกอบทั้งหมดในช่วงที่สามารถปรับปรุงการอ่าน สมมติว่าการกระทำที่คุณต้องการดำเนินการนั้นมีความยาว 20 บรรทัดและฟังก์ชันที่การดำเนินการนั้นมีความยาวประมาณ 20 บรรทัด นั่นจะทำให้ฟังก์ชั่น 40 บรรทัดมีความยาวแบบธรรมดาสำหรับลูปและเพียงประมาณ 20 ด้วยstd::for_eachจึงน่าจะง่ายต่อการเข้าใจ

std::for_eachมีแนวโน้มที่จะเป็นคนธรรมดามากขึ้นสำหรับ Functors และนำมาใช้ใหม่เช่น:

struct DeleteElement
{
    template <typename T>
    void operator()(const T *ptr)
    {
        delete ptr;
    }
};

และในรหัสที่คุณมีเพียงหนึ่งซับstd::for_each(v.begin(), v.end(), DeleteElement())ที่ IMO ดีกว่าเล็กน้อยอย่างชัดเจนห่วง

โดยทั่วไปแล้วฟังก์ชั่นทั้งหมดนั้นง่ายกว่าที่จะทดสอบภายใต้หน่วยมากกว่าการวนซ้ำในช่วงกลางของฟังก์ชั่นที่ยาวนานและการชนะเพียงครั้งเดียวก็เป็นเรื่องใหญ่สำหรับฉัน

std::for_each โดยทั่วไปก็มีความน่าเชื่อถือมากกว่าเนื่องจากคุณมีโอกาสน้อยที่จะทำผิดพลาดกับช่วง

และท้ายที่สุดคอมไพเลอร์อาจสร้างโค้ดที่ดีกว่าเล็กน้อยstd::for_eachสำหรับบางประเภทของงานฝีมือที่ทำด้วยมือสำหรับลูปเนื่องจาก (for_each) มักจะดูเหมือนกันสำหรับคอมไพเลอร์และผู้เขียนคอมไพเลอร์สามารถใส่ความรู้ทั้งหมดเพื่อให้ดีที่สุด สามารถ.

เช่นเดียวกับขั้นตอนวิธีการมาตรฐานอื่น ๆ เช่นfind_if, transformฯลฯ


2

forสำหรับลูปที่สามารถวนซ้ำแต่ละองค์ประกอบหรือทุก ๆ สามเป็นต้นfor_eachสำหรับวนซ้ำแต่ละองค์ประกอบเท่านั้น ชัดเจนจากชื่อของมัน ดังนั้นชัดเจนว่าคุณตั้งใจจะทำอะไรในโค้ดของคุณ


4
ไม่ว่าคุณจะผ่านมัน iterator ซึ่งความก้าวหน้าโดย 3 ++กับแต่ละ อาจจะผิดปกติ แต่สำหรับ for-loop ก็ทำเช่นเดียวกัน
Potatoswatter

ในกรณีนั้นอาจเป็นการดีกว่าถ้าใช้transformเพื่อไม่ให้ใครสับสน
Kirill V. Lyadvinsky

2

หากคุณใช้อัลกอริธึมอื่นจาก STL บ่อยครั้งมีข้อดีหลายประการfor_eachดังนี้:

  1. มันมักจะง่ายกว่าและเกิดข้อผิดพลาดน้อยกว่าสำหรับลูปส่วนหนึ่งเป็นเพราะคุณจะคุ้นเคยกับฟังก์ชั่นการใช้งานกับอินเทอร์เฟซนี้และอีกส่วนหนึ่งเพราะจริงๆแล้วมันค่อนข้างกระชับในหลายกรณี
  2. แม้ว่าช่วงของการวนรอบจะง่ายกว่า แต่ก็มีความยืดหยุ่นน้อยกว่า (ดังที่เอเดรียนแม็คคาร์ธีระบุไว้ แต่มันวนซ้ำทั่วทั้งคอนเทนเนอร์)
  3. ซึ่งแตกต่างจากแบบดั้งเดิมสำหรับวงfor_eachบังคับให้คุณเขียนรหัสที่จะทำงานสำหรับ iterator การป้อนข้อมูลใด ๆ การถูก จำกัด ด้วยวิธีนี้อาจเป็นสิ่งที่ดีเพราะ:

    1. คุณอาจจำเป็นต้องปรับเปลี่ยนรหัสเพื่อใช้งานกับคอนเทนเนอร์อื่นในภายหลัง
    2. ในตอนแรกมันอาจสอนอะไรคุณและ / หรือเปลี่ยนนิสัยของคุณให้ดีขึ้น
    3. แม้ว่าคุณมักจะเขียนลูปซึ่งเทียบเท่าได้อย่างสมบูรณ์แบบคนอื่น ๆ for_eachที่ปรับเปลี่ยนรหัสเดียวกันอาจจะไม่ทำเช่นนี้โดยไม่ต้องได้รับแจ้งให้ใช้
  4. for_eachบางครั้งการใช้ทำให้ชัดเจนมากขึ้นว่าคุณสามารถใช้ฟังก์ชัน STL ที่เฉพาะเจาะจงมากขึ้นเพื่อทำสิ่งเดียวกัน (เช่นเดียวกับตัวอย่างของ Jerry Coffin มันไม่จำเป็นว่าfor_eachเป็นตัวเลือกที่ดีที่สุด แต่สำหรับลูปไม่ใช่ทางเลือกเดียวเท่านั้น)


2

ด้วย C ++ 11 และสองแม่แบบง่าย ๆ คุณสามารถเขียน

        for ( auto x: range(v1+4,v1+6) ) {
                x*=2;
                cout<< x <<' ';
        }

เพื่อทดแทนfor_eachหรือห่วง เหตุใดจึงเลือกที่จะลดความกะทัดรัดและความปลอดภัยลงจึงไม่มีโอกาสผิดพลาดในการแสดงออกที่ไม่มีอยู่

สำหรับฉันfor_eachดีกว่าเสมอในบริเวณเดียวกันเมื่อร่างกายลูปเป็นนักแสดงแล้วและฉันจะใช้ประโยชน์จากสิ่งที่ฉันได้รับ

คุณยังคงใช้สามนิพจน์forแต่ตอนนี้เมื่อคุณเห็นสิ่งที่คุณรู้ว่ามีบางสิ่งที่จะเข้าใจมีมันไม่ได้สำเร็จรูป ฉันเกลียดหม้อไอน้ำ ฉันไม่พอใจการดำรงอยู่ของมัน ไม่ใช่รหัสจริงไม่มีอะไรให้เรียนรู้โดยการอ่านมันเป็นอีกสิ่งหนึ่งที่ต้องตรวจสอบ ความพยายามทางจิตสามารถวัดได้โดยง่ายเพียงวิธีการเป็นสนิมในการตรวจสอบ

แม่แบบคือ

template<typename iter>
struct range_ { 
                iter begin() {return __beg;}    iter end(){return __end;}
            range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
            iter __beg, __end;
};

template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
    { return range_<iter>(begin,end); }

1

ส่วนใหญ่คุณจะต้องย้ำกว่าคอลเลกชันทั้งหมด ดังนั้นฉันขอแนะนำให้คุณเขียนตัวแปร for_each () ของคุณเองโดยรับเพียง 2 พารามิเตอร์ สิ่งนี้จะช่วยให้คุณเขียนตัวอย่างของ Terry Mahaffeyเป็น:

for_each(container, [](int& i) {
    i += 10;
});

ฉันคิดว่านี่สามารถอ่านได้มากกว่าการวนซ้ำ อย่างไรก็ตามสิ่งนี้ต้องการส่วนขยายคอมไพเลอร์ C ++ 0x


1

ฉันคิดว่า for_each นั้นไม่ดีสำหรับการอ่านได้ แนวคิดนี้ดี แต่ c ++ ทำให้ยากต่อการเขียนอ่านอย่างน้อยสำหรับฉัน การแสดงออก lamda c ++ 0x จะช่วย ฉันชอบความคิดของลำดาส อย่างไรก็ตามในครั้งแรกที่ฉันคิดว่าไวยากรณ์น่าเกลียดมากและฉันไม่แน่ใจ 100% ว่าฉันจะชินกับมัน บางทีใน 5 ปีฉันจะชินกับมันและไม่ให้ความคิดที่สอง แต่อาจจะไม่ เวลาจะบอกเอง :)

ฉันชอบที่จะใช้

vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
  // Do stuff
}

ฉันพบความชัดเจนในการวนลูปที่ชัดเจนขึ้นในการอ่านและการอธิบายโดยใช้ตัวแปรที่กำหนดชื่อสำหรับตัววนซ้ำเริ่มต้นและจุดสิ้นสุดลดความยุ่งเหยิงในลูป for

แน่นอนกรณีแตกต่างกันไปนี่เป็นเพียงสิ่งที่ฉันมักจะหาที่ดีที่สุด


0

คุณสามารถให้ตัววนซ้ำเป็นสายไปยังฟังก์ชันที่ดำเนินการในแต่ละรอบซ้ำผ่านการวนซ้ำ

ดูที่นี่: http://www.cplusplus.com/reference/algorithm/for_each/


2
โพสต์เฉพาะลิงก์ไม่ได้คำตอบที่ดี แต่อย่างใดลิงก์นี้แสดงอะไรที่คล้ายกับตัววนซ้ำแบบเรียกได้ ฉันค่อนข้างมั่นใจว่าคอนเซปต์ก็ไม่สมเหตุสมผล บางทีคุณอาจกำลังสรุปสิ่งที่for_eachทำในกรณีนี้มันไม่ตอบคำถามเกี่ยวกับข้อดีของมัน
underscore_d


0

for_eachอนุญาตให้เรานำรูปแบบ Fork-Joinมาใช้ อื่น ๆ กว่าที่จะสนับสนุนได้อย่างคล่องแคล่วอินเตอร์เฟซ

รูปแบบการเข้าร่วมส้อม

เราสามารถเพิ่มการนำgpu::for_eachไปใช้เพื่อใช้ cuda / gpu สำหรับการคำนวณแบบต่างกันโดยการเรียกใช้งานแลมบ์ดาในพนักงานหลายคน

gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.

และgpu::for_eachอาจรอให้คนงานทำงานกับแลมบ์ดา - ภารกิจให้เสร็จก่อนที่จะดำเนินการกับข้อความถัดไป

อินเตอร์เฟซได้อย่างคล่องแคล่ว

มันช่วยให้เราสามารถเขียนโค้ดที่มนุษย์อ่านได้อย่างกระชับ

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.