vector :: at กับ vector :: operator []


95

ฉันรู้ว่าat()มันช้ากว่า[]เนื่องจากการตรวจสอบขอบเขตซึ่งจะกล่าวถึงในคำถามที่คล้ายกันเช่นC ++ Vector ที่ / [] ความเร็วของตัวดำเนินการ หรือ:: std :: vector :: at () vs operator [] << ผลลัพธ์ที่น่าประหลาดใจ !! ช้าลง 5 ถึง 10 เท่า / เร็วขึ้น! . ฉันไม่เข้าใจว่าat()วิธีนี้ดีสำหรับอะไร

ถ้าฉันมีเวกเตอร์ธรรมดา ๆ แบบนี้: std::vector<int> v(10);และฉันตัดสินใจที่จะเข้าถึงองค์ประกอบของมันโดยใช้at()แทน[]ในสถานการณ์เมื่อฉันมีดัชนีiและฉันไม่แน่ใจว่ามันอยู่ในขอบเขตเวกเตอร์หรือไม่มันบังคับให้ฉันรวมมันด้วยการลองจับ บล็อก :

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

แม้ว่าฉันจะสามารถรับพฤติกรรมเดียวกันได้โดยใช้size()และตรวจสอบดัชนีด้วยตัวเองซึ่งดูเหมือนง่ายและสะดวกมากสำหรับฉัน:

if (i < v.size())
    v[i] = 2;

คำถามของฉันคือ
ข้อดีของการใช้vector :: at over vector :: operator []คืออะไร?
เมื่อฉันควรใช้เวกเตอร์ :: ที่มากกว่าเวกเตอร์ :: ขนาด + เวกเตอร์ :: ผู้ประกอบการ [] ?


11
+1 คำถามดีมาก !! แต่ฉันไม่คิดว่าที่ () เป็นสิ่งที่ใช้กันทั่วไป
Rohit Vipin Mathews

10
โปรดทราบว่าในโค้ดตัวอย่างของคุณif (i < v.size()) v[i] = 2;มีเส้นทางรหัสที่เป็นไปได้ที่ไม่ได้กำหนดให้2กับองค์ประกอบใด ๆvเลย หากนั่นเป็นพฤติกรรมที่ถูกต้องเยี่ยมมาก แต่มักไม่มีอะไรสมเหตุสมผลที่ฟังก์ชันนี้จะทำได้เมื่อi >= v.size()ใด ดังนั้นจึงไม่มีเหตุผลเฉพาะว่าทำไมจึงไม่ควรใช้ข้อยกเว้นเพื่อระบุสถานการณ์ที่ไม่คาดคิด ฟังก์ชั่นมากมายใช้งานได้operator[]โดยไม่ต้องตรวจสอบขนาดเอกสารที่iต้องอยู่ในระยะและตำหนิ UB ที่เป็นผลลัพธ์ในผู้โทร
Steve Jessop

คำตอบ:


74

ฉันจะบอกว่าข้อยกเว้นที่vector::at()พ่นไม่ได้ตั้งใจที่จะจับรหัสโดยรอบทันที ส่วนใหญ่มีประโยชน์ในการจับข้อบกพร่องในโค้ดของคุณ หากคุณต้องการตรวจสอบขอบเขตที่รันไทม์เพราะเช่นดัชนีมาจากอินพุตของผู้ใช้คุณจะดีที่สุดด้วยifคำสั่ง ดังนั้นโดยสรุปให้ออกแบบโค้ดของคุณด้วยความตั้งใจที่vector::at()จะไม่ทำให้เกิดข้อยกเว้นดังนั้นหากเป็นเช่นนั้นและโปรแกรมของคุณยกเลิกก็เป็นสัญญาณของข้อบกพร่อง (เช่นเดียวกับassert())


1
+1 ฉันชอบคำอธิบายเกี่ยวกับวิธีแยกการจัดการอินพุตของผู้ใช้ที่ไม่ถูกต้อง (การตรวจสอบความถูกต้องอาจคาดว่าอินพุตที่ไม่ถูกต้องดังนั้นจึงไม่ถือว่าเป็นสิ่งพิเศษ) ... สิ่ง)
Bojan Komazec

คุณจึงบอกว่าฉันควรใช้size()+ []เมื่อดัชนีขึ้นอยู่กับอินพุตของผู้ใช้ใช้assertในสถานการณ์ที่ดัชนีไม่ควรอยู่นอกขอบเขตเพื่อแก้ไขข้อบกพร่องได้ง่ายในอนาคตและ.at()ในสถานการณ์อื่น ๆ ทั้งหมด (ในกรณีที่อาจเกิดสิ่งผิดปกติขึ้น .. .)
LihO

8
@LihO: หากการใช้งานของคุณเสนอการใช้งานการดีบักvectorก็น่าจะดีกว่าถ้าใช้เป็นตัวเลือก "ในกรณี" แทนที่จะใช้at()ทุกที่ ด้วยวิธีนี้คุณสามารถหวังว่าจะมีประสิทธิภาพเพิ่มขึ้นอีกเล็กน้อยในโหมดรีลีสในกรณีที่คุณต้องการ
Steve Jessop

3
ใช่แล้วการใช้งาน STL ส่วนใหญ่ในปัจจุบันรองรับโหมดดีบักซึ่ง จำกัด การตรวจสอบoperator[]เช่นgcc.gnu.org/onlinedocs/libstdc++/manual/…ดังนั้นหากแพลตฟอร์มของคุณรองรับสิ่งนี้คุณอาจจะไม่ทำสิ่งนี้!
pmdj

1
@pmdj จุดที่ยอดเยี่ยมซึ่งฉันไม่รู้เกี่ยวกับ ... แต่ลิงก์กำพร้า : P ปัจจุบันคือ: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
underscore_d

16

มันบังคับให้ฉันปิดกั้นด้วยบล็อกลองจับ

ไม่มันไม่มี (บล็อก try / catch สามารถอัปสตรีมได้) มีประโยชน์เมื่อคุณต้องการให้มีการยกเว้นยกเว้นโปรแกรมของคุณเพื่อเข้าสู่ขอบเขตพฤติกรรมที่ไม่ได้กำหนด

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

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


ถ้าฉันไม่จับout_of_rangeข้อยกเว้นก็abort()จะถูกเรียก
LihO

@LihO: ไม่จำเป็น.. try..catchสามารถอยู่ในเมธอดที่เรียกเมธอดนี้ได้
Naveen

12
ถ้าไม่มีอะไรจะเป็นประโยชน์เท่าที่คุณมิฉะนั้นจะพบว่าตัวเองเขียนสิ่งที่ต้องการat if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }ผู้คนมักจะนึกถึงฟังก์ชันที่มีข้อยกเว้นในแง่ของ "คำสาปฉันต้องจัดการข้อยกเว้น" แต่ตราบใดที่คุณบันทึกอย่างละเอียดว่าแต่ละฟังก์ชันของคุณสามารถโยนอะไรได้ก็ยังสามารถใช้ว่า "เยี่ยมมากฉันไม่ทำ ต้องตรวจสอบเงื่อนไขและมีข้อยกเว้น ".
Steve Jessop

@SteveJessop: ฉันไม่ชอบโยนข้อยกเว้นสำหรับข้อบกพร่องของโปรแกรมเนื่องจากโปรแกรมเมอร์คนอื่นสามารถจับได้ การยืนยันมีประโยชน์มากกว่าที่นี่
Alexandre C.

6
@AlexandreC. การตอบสนองอย่างเป็นทางการนั้นout_of_rangeมาจากlogic_errorและโปรแกรมเมอร์คนอื่น ๆ "ควร" รู้ดีกว่าที่จะจับlogic_errorต้นน้ำและเพิกเฉยต่อพวกเขา assertสามารถเพิกเฉยได้เช่นกันหากเพื่อนร่วมงานของคุณไม่อยากรู้เกี่ยวกับข้อผิดพลาดของพวกเขามันยากกว่าเพราะพวกเขาต้องรวบรวมรหัสของคุณด้วยNDEBUG;-) แต่ละกลไกมีข้อดีและข้อบกพร่อง
Steve Jessop

11

ข้อดีของการใช้ vector :: at over vector :: operator [] คืออะไร? เมื่อใดที่ฉันควรใช้ vector :: at แทนที่จะใช้ vector :: size + vector :: operator []

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

นอกจากนี้ยังเป็นที่น่าสังเกตว่าในโค้ดบางประเภทดัชนีถูกเพิ่มขึ้นในรูปแบบที่ซับซ้อนและใช้อย่างต่อเนื่องเพื่อค้นหาอาร์เรย์ at()ในกรณีเช่นนี้มันง่ายมากเพื่อให้แน่ใจว่าการตรวจสอบที่ถูกต้องโดยใช้

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

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

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


10

at จะชัดเจนขึ้นหากคุณมีตัวชี้ไปที่เวกเตอร์:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

นอกเหนือจากประสิทธิภาพสิ่งแรกคือรหัสที่ง่ายและชัดเจนกว่า


... โดยเฉพาะอย่างยิ่งเมื่อคุณต้องการตัวชี้ไปยังองค์ประกอบที่nของเวกเตอร์
ปลาโลมา

4

ขั้นแรกไม่ระบุว่าช้ากว่าat()หรือoperator[]ไม่ เมื่อไม่มีข้อผิดพลาดเกี่ยวกับขอบเขตฉันคาดว่าพวกเขาจะมีความเร็วเท่ากันอย่างน้อยก็ในการดีบักงานสร้าง ความแตกต่างคือat()ระบุว่าจะเกิดอะไรขึ้นในนั้นมีข้อผิดพลาดขอบเขต (ข้อยกเว้น) โดยที่ในกรณีoperator[]นี้เป็นพฤติกรรมที่ไม่ได้กำหนด - ความผิดพลาดในระบบทั้งหมดที่ฉันใช้ (g ++ และ VC ++) อย่างน้อยก็เมื่อ ใช้แฟล็กการดีบักปกติ (ข้อแตกต่างอีกประการหนึ่งคือเมื่อฉันแน่ใจในรหัสของฉันฉันจะได้รับความเร็วเพิ่มขึ้นอย่างมากoperator[] โดยการปิดการดีบักหากประสิทธิภาพต้องการ - ฉันจะไม่ทำเว้นแต่ว่าจำเป็น)

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


4
ทำไมคุณถึงคาดหวังว่าพวกเขาจะมีความเร็วเท่ากันเมื่อoperator[]ใดที่ไม่ถูกบังคับให้ตรวจสอบขอบเขตในขณะที่at()? คุณหมายถึงปัญหาการแคชการเก็งกำไรและการแยกสาขาบัฟเฟอร์หรือไม่?
Sebastian Mach

3
ขออภัยคุณไม่ได้รับแอตทริบิวต์ "ในโหมดแก้ไขข้อบกพร่อง" อย่างไรก็ตามฉันจะไม่วัดคุณภาพของโค้ดในโหมดดีบัก at()ในโหมดการเปิดตัวการตรวจสอบเป็นสิ่งจำเป็นโดยเฉพาะ
Sebastian Mach

1
@phresnel รหัสส่วนใหญ่ที่ฉันส่งไปอยู่ในโหมด "ดีบัก" คุณจะปิดการตรวจสอบเมื่อจำเป็นต้องมีปัญหาด้านประสิทธิภาพเท่านั้น (Microsoft ก่อนปี 2010 เป็นปัญหาเล็กน้อยที่นี่เนื่องจากstd::stringไม่ได้ผลเสมอไปหากตัวเลือกการตรวจสอบไม่สอดคล้องกับรันไทม์: -MDและคุณควรปิดการตรวจสอบ-MDdดีกว่าและคุณควรมี )
James Kanze

3
ฉันเป็นค่ายที่บอกว่า "code as sanctioned (การันตี) ตามมาตรฐาน"; แน่นอนว่าคุณมีอิสระที่จะส่งมอบในโหมดดีบัก แต่เมื่อทำการพัฒนาข้ามแพลตฟอร์ม (รวมถึง แต่ไม่ใช่เฉพาะกรณีของระบบปฏิบัติการเดียวกัน แต่เป็นเวอร์ชันคอมไพเลอร์ที่แตกต่างกัน) การใช้มาตรฐานเป็นทางออกที่ดีที่สุดสำหรับการเผยแพร่และโหมดดีบัก ถือเป็นเครื่องมือสำหรับโปรแกรมเมอร์ที่จะทำให้สิ่งนั้นถูกต้องและแข็งแกร่งเป็นส่วนใหญ่ :)
Sebastian Mach

1
ในทางกลับกันหากส่วนที่สำคัญของแอปพลิเคชันของคุณถูกแยกออกและได้รับการปกป้องโดยเช่นความปลอดภัยข้อยกเว้น (RAII ftw) การเข้าถึงทุกครั้งoperator[]ควรทำให้พิการหรือไม่? เช่นstd::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y).... ฉันคิดว่าการบังคับใช้การตรวจสอบขอบเขตบนไบนารีที่จัดส่งอยู่ภายใต้การมองในแง่ร้ายก่อนเวลาอันควร อิมโฮควรเป็นตัวช่วยสำหรับโค้ดที่ไม่ได้รับการออกแบบมาอย่างดีเท่านั้น
Sebastian Mach

1

จุดรวมของการใช้ข้อยกเว้นคือรหัสการจัดการข้อผิดพลาดของคุณอาจอยู่ห่างออกไป

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

นอกจากนี้คอนเทนเนอร์อื่น ๆ เช่นstd::mapยังมีstd::map::atซึ่งมีความหมายแตกต่างจากstd::map::operator[]: at สามารถใช้บนแผนที่ const ได้ในขณะที่operator[]ไม่สามารถใช้ได้ ตอนนี้ถ้าคุณต้องการที่จะเขียนรหัสภาชนะไม่เชื่อเรื่องพระเจ้าเหมือนสิ่งที่สามารถจัดการกับอย่างใดอย่างหนึ่งconst std::vector<T>&หรือconst std::map<std::size_t, T>&, ContainerType::atจะเป็นอาวุธที่คุณเลือก

อย่างไรก็ตามกรณีเหล่านี้ทั้งหมดมักจะปรากฏขึ้นเมื่อจัดการการป้อนข้อมูลบางประเภทที่ไม่ได้รับการตรวจสอบ ถ้าคุณแน่ใจว่าเกี่ยวกับช่วงที่ถูกต้องของคุณที่คุณมักจะควรจะเป็นคุณจะสามารถใช้operator[]แต่ยังดีกว่า iterators ด้วยและbegin()end()


1

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


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

ขอบคุณสำหรับทิป. ตอนนี้ได้รับการแก้ไขแล้ว
ahj

0

หมายเหตุ:ดูเหมือนว่ามีคนใหม่บางคนลงคะแนนคำตอบนี้โดยไม่ได้รับความอนุเคราะห์จากการบอกสิ่งที่ผิด ด้านล่างนี้คือคำตอบที่ถูกต้องและสามารถตรวจสอบได้ที่นี่

มีข้อแตกต่างเพียงอย่างเดียว: atตรวจสอบขอบเขตในขณะที่operator[]ไม่ได้ สิ่งนี้ใช้กับการดีบักบิลด์และรีลีสบิลด์และมาตรฐานนี้ระบุไว้เป็นอย่างดี มันง่ายมาก

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


1
ข้อยกเว้นใน C ++ หมายถึงกลไกการจัดการข้อผิดพลาดไม่ใช่เครื่องมือสำหรับการดีบัก สมุนไพร Sutter อธิบายว่าทำไมการขว้างปาstd::out_of_rangeหรือรูปแบบใด ๆstd::logic_errorในความเป็นจริงข้อผิดพลาดตรรกะและของตัวเองที่นี่
Big Temp

@BigTemp - ฉันไม่แน่ใจว่าความคิดเห็นของคุณเกี่ยวข้องกับคำถามและคำตอบนี้อย่างไร ใช่ข้อยกเว้นเป็นหัวข้อที่ถกเถียงกันมาก แต่คำถามที่นี่คือความแตกต่างระหว่างatและ[]และคำตอบของฉันเพียงแค่ระบุความแตกต่าง ผมเองใช้วิธี "ปลอดภัย" เมื่อ perf ไม่ใช่ปัญหา ดังที่ Knuth กล่าวว่าอย่าทำการเพิ่มประสิทธิภาพก่อนเวลาอันควร นอกจากนี้ยังเป็นการดีที่จะได้รับข้อบกพร่องก่อนการผลิตโดยไม่คำนึงถึงความแตกต่างทางปรัชญา
Shital Shah
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.