ต้องการอัลกอริทึมไปยังลูปที่เขียนด้วยมือไหม


10

ข้อใดต่อไปนี้ที่คุณพบว่าสามารถอ่านได้มากขึ้น วงเขียนด้วยมือ:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

หรืออัลกอริทึมภาวนา:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

ฉันสงสัยว่าstd::for_eachมันคุ้มค่าจริงหรือเปล่าจากตัวอย่างง่ายๆที่ต้องใช้รหัสจำนวนมาก

คุณคิดอย่างไรกับเรื่องนี้?


2
สำหรับ C ++ คำตอบนั้นชัดเจนมาก แต่ในภาษาอื่น ๆ ทั้งสองมีค่าเท่ากัน (เช่น Python: map(bar.process, vec)แม้ว่าแผนที่สำหรับผลข้างเคียงจะหมดกำลังใจและแนะนำให้ใช้รายการความเข้าใจ / ตัวสร้างนิพจน์บนแผนที่)

5
จากนั้นก็มีBOOST_FOREACH...
เบนจามินแบนเนอร์

ดังนั้นรายการใน STL ที่มีประสิทธิภาพไม่ได้โน้มน้าวคุณ stackoverflow.com/questions/135129/…
งาน

คำตอบ:


18

มีเหตุผลที่ทำให้ลูกแกะได้รับการแนะนำและเป็นเพราะแม้แต่ Commitee มาตรฐานก็ยอมรับว่ารูปแบบที่สองนั้นดูด ใช้แบบฟอร์มแรกจนกว่าคุณจะได้รับการสนับสนุน C ++ 0x และ lambda


2
การใช้เหตุผลแบบเดียวกันสามารถพิสูจน์รูปแบบที่สองได้: คณะกรรมการมาตรฐานยอมรับว่ามันยอดเยี่ยมกว่ามากที่พวกเขาแนะนำลูกแกะเพื่อทำให้ดีขึ้น :-p (+1 อย่างไรก็ตาม)
Konrad Rudolph

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

หมายเหตุ: การสนับสนุนแลมบ์ดาถูกเพิ่มเข้ามาเนื่องจากมีข้อร้องเรียนหลักสองประการ 1) มาตรฐานสำเร็จรูปจำเป็นต้องผ่านพารามิเตอร์เพื่อ functors 2) ไม่มีรหัสที่จุดโทร รหัสของคุณไม่เป็นไปตามที่คุณต้องการเพียงแค่เรียกใช้เมธอด ดังนั้น 1) ไม่มีโค้ดพิเศษสำหรับการส่งพารามิเตอร์ 2) โค้ดไม่ได้อยู่ที่จุดโทรดังนั้นแลมบ์ดาจะไม่ปรับปรุงการบำรุงรักษาโค้ด ดังนั้นใช่แลมบ์ดาเป็นส่วนเสริมที่ยอดเยี่ยม นี่ไม่ใช่ข้อโต้แย้งที่ดีสำหรับพวกเขา
Martin York

9

ใช้ชุดตัวเลือกที่อธิบายสิ่งที่คุณตั้งใจจะทำดีที่สุด นั่นคือ

สำหรับแต่ละองค์ประกอบxในการทำvecbar.process(x)

ตอนนี้เรามาตรวจสอบตัวอย่าง:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

เรามีfor_eachมีมากเกินไป - yippeh เรามี[begin; end)ช่วงที่เราต้องการดำเนินการ

โดยหลักการแล้วอัลกอริทึมนั้นชัดเจนกว่าและชอบมากกว่าการเขียนด้วยมือใด ๆ แต่แล้ว ... ยึดประสาน? Memfun? โดยทั่วไป C ++ interna ของวิธีการรับฟังก์ชั่นสมาชิก? สำหรับงานของฉันฉันไม่สนใจพวกเขา! ฉันไม่ต้องการที่จะทุกข์ทรมานจาก verbose ไวยากรณ์น่าขนลุกนี้

ตอนนี้ความเป็นไปได้อื่น ๆ :

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

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

ยอมรับมันดู waay ดีกว่าวิธีแรก (อย่างน้อยห่วงร่างกายมีความยืดหยุ่นและค่อนข้างชัดเจน) แต่ยังคงมันไม่ได้จริงๆที่ดี เราจะใช้อันนี้ถ้าเราไม่มีความเป็นไปได้ที่ดีขึ้น แต่บางทีเราอาจมี ...

วิธีที่ดีกว่า

for_eachตอนนี้กลับไป จะดีหรือไม่ถ้าพูดอย่างแท้จริงfor_each และมีความยืดหยุ่นในการทำงานที่ต้องทำด้วย? โชคดีที่ตั้งแต่ C ++ 0x lambdas เราเป็น

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

ตอนนี้เราได้ค้นพบวิธีแก้ปัญหาทั่วไปที่เป็นนามธรรมในหลาย ๆ สถานการณ์ที่เกี่ยวข้องแล้วในกรณีนี้เรามีรายการโปรด # 1 ที่แน่นอน:

foreach(const Foo& x, vec) bar.process(x);

มันไม่ได้ชัดเจนกว่านั้นจริงๆ โชคดีที่ C ++ 0x มีไวยากรณ์ในตัวที่คล้ายกัน!


2
กล่าวว่า "ไวยากรณ์ที่คล้ายกัน" ถูกfor (const Foo& x : vec) bar.process(x);หรือใช้const auto&ถ้าคุณต้องการ
Jon Purdy

const auto&เป็นไปได้? ไม่ทราบว่าเป็นข้อมูลที่ดี!
Dario

8

เพราะสิ่งนี้อ่านไม่ได้เหรอ?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
ขออภัยด้วยเหตุผลบางอย่างมันบอกว่า "ตรวจสอบ" ที่ฉันเชื่อว่ารหัสของคุณควรจะเป็น
Mateen Ulhaq

6
รหัสของคุณให้คำเตือนคอมไพเลอร์เนื่องจากการเปรียบเทียบintกับ a size_tนั้นเป็นอันตราย นอกจากนี้การสร้างดัชนีไม่สามารถใช้ได้กับทุกคอนเทนเนอร์เช่นstd::setกัน
fredoverflow

2
รหัสนี้จะใช้ได้เฉพาะกับคอนเทนเนอร์ที่มีตัวดำเนินการทำดัชนีซึ่งมีจำนวนน้อย มันเชื่อมโยงอัลกอริธึมลงอย่างไร้เหตุผล - จะเกิดอะไรขึ้นถ้าแผนที่ <> เหมาะสมกว่า ต้นฉบับสำหรับลูปและ for_each จะไม่ได้รับผลกระทบ
JBRWilkinson

2
และคุณออกแบบรหัสสำหรับเวกเตอร์กี่ครั้งแล้วจึงตัดสินใจสลับเป็นแผนที่ ราวกับว่าการออกแบบสำหรับสิ่งนั้นมีความสำคัญทางภาษา
Martin Beckett

2
การวนซ้ำตามคอลเลกชันโดยดัชนีนั้นไม่ได้รับการสนับสนุนเนื่องจากบางคอลเลกชันมีประสิทธิภาพต่ำในการจัดทำดัชนีในขณะที่คอลเลกชันทั้งหมดจะทำงานได้ดีเมื่อใช้ตัววนซ้ำ
slaphappy

3

โดยทั่วไปแล้วรูปแบบแรกนั้นสามารถอ่านได้โดยทุกคนที่รู้ว่า for-loop นั้นคืออะไร

โดยทั่วไปแล้วสิ่งที่สองนั้นไม่สามารถอ่านได้เลย: ง่ายพอที่จะเข้าใจว่า for_each ทำอะไร แต่ถ้าคุณไม่เคยเห็นstd::bind1st(std::mem_fun_refฉันสามารถจินตนาการได้ว่ามันยากที่จะเข้าใจ


2

จริงๆแล้วแม้กับ C ++ 0x ฉันไม่แน่ใจว่าfor_eachจะได้รับความรักมากมาย

for(Foo& foo: vec) { bar.process(foo); }

เป็นวิธีที่อ่านง่ายขึ้น

สิ่งหนึ่งที่ฉันไม่ชอบเกี่ยวกับอัลกอริทึม (ใน C ++) คือพวกมันให้เหตุผลกับตัววนซ้ำทำให้ใช้คำสั่งที่ละเอียดมาก


2

หากคุณเขียนบาร์ในฐานะนักแสดงแล้วมันจะง่ายกว่านี้มาก:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

ที่นี่รหัสสามารถอ่านได้ค่อนข้าง


2
มันค่อนข้างอ่านได้นอกเหนือจากข้อเท็จจริงที่ว่าคุณเพิ่งเขียน functor
Winston Ewert

ดูที่นี่สำหรับสาเหตุที่ฉันคิดว่ารหัสนี้แย่ลง
sbi

1

ฉันชอบอันหลังเพราะมันสะอาดและเรียบร้อย จริง ๆ แล้วมันเป็นส่วนหนึ่งของภาษาอื่น ๆ แต่ใน C ++ มันเป็นส่วนหนึ่งของห้องสมุด มันไม่สำคัญจริงๆ


0

ตอนนี้ปัญหานี้ไม่ได้เฉพาะเจาะจงสำหรับ C ++ ฉันจะเรียกร้อง

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

การใช้งานทั่วไปที่ฉันเห็นมากในภาษาอื่นจะเป็น:

someCollection.forEach(someUnaryFunctor);

ข้อดีของสิ่งนี้คือคุณไม่จำเป็นต้องรู้ว่าsomeCollectionมีการนำไปใช้จริงหรือsomeUnaryFunctorไม่ สิ่งที่คุณต้องรู้คือว่าforEachวิธีการจะทำซ้ำองค์ประกอบทั้งหมดของคอลเลกชันผ่านพวกเขาไปยังฟังก์ชั่นที่กำหนด

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

นอกจากนี้คุณควรจำไว้ว่าวิธีการทำงานนั้นช้ากว่าเพราะคุณมีการโทรจำนวนมากซึ่งมีค่าใช้จ่ายจำนวนหนึ่งซึ่งคุณไม่ต้องกังวล


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

@Andres F: ดังนั้นนี่ดูสะอาดกว่าเหรอ? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));และจะช้าลงในเวลารันไทม์หรือเวลารวบรวม (ถ้าคุณโชคดี) ฉันไม่เห็นว่าทำไมการแทนที่โครงสร้างการควบคุมการไหลด้วยการเรียกใช้ฟังก์ชันทำให้โค้ดดีขึ้น เกี่ยวกับทางเลือกที่นำเสนอที่นี่สามารถอ่านได้มากขึ้น
back2dos

0

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

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


0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.