เหตุใดเวกเตอร์ <bool> จึงไม่เป็นคอนเทนเนอร์ STL


106

รายการที่ 18 ของสกอตต์เมเยอร์สหนังสือSTL มีผลบังคับใช้ 50 วิธีที่เฉพาะเจาะจงในการปรับปรุงการใช้งานของคุณของไลบรารีแม่แบบมาตรฐานกล่าวว่าเพื่อหลีกเลี่ยงการvector <bool>ที่มันไม่ได้เป็นภาชนะ STL และมันไม่ได้โดดถือbools

รหัสต่อไปนี้:

vector <bool> v; 
bool *pb =&v[0];

จะไม่คอมไพล์ละเมิดข้อกำหนดของคอนเทนเนอร์ STL

ข้อผิดพลาด:

cannot convert 'std::vector<bool>::reference* {aka std::_Bit_reference*}' to 'bool*' in initialization

vector<T>::operator []return type ควรจะเป็นT&แต่ทำไมถึงเป็นกรณีพิเศษสำหรับvector<bool>?

อะไรvector<bool>จริงๆประกอบด้วย?

รายการกล่าวเพิ่มเติมว่า:

deque<bool> v; // is a STL container and it really contains bools

สามารถใช้เป็นทางเลือกอื่นได้vector<bool>หรือไม่?

ใครช่วยอธิบายเรื่องนี้ได้ไหม


23
มันเป็นข้อผิดพลาดในการออกแบบใน C ++ 98 ซึ่งตอนนี้ยังคงไว้สำหรับความเข้ากันได้
Oktalist

8
@ g-makulik ไม่ใช่ว่าการใช้มันจะไม่คอมไพล์เพียง แต่คุณไม่สามารถจัดเก็บที่อยู่ขององค์ประกอบในตัวชี้ไปboolได้เนื่องจากองค์ประกอบนั้นไม่มีที่อยู่ของตัวเอง
chris

2
บางทีนี่อาจจะช่วยได้: stackoverflow.com/questions/670308/alternative-to-vectorbool
chris

1
@ g-makulik std::vector<bool> v;จะรวบรวม. &v[0]จะไม่ (รับที่อยู่ชั่วคราว)
Oktalist

4
vector<bool>มีตัวแทนที่ไม่ดี แต่ไม่สามารถแก้ไขได้ทั้งหมด: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

คำตอบ:


122

ด้วยเหตุผลด้านการเพิ่มประสิทธิภาพพื้นที่มาตรฐาน C ++ (ย้อนกลับไปถึง C ++ 98) จึงเรียกใช้อย่างชัดเจน vector<bool>ว่าเป็นคอนเทนเนอร์มาตรฐานพิเศษโดยแต่ละบูลใช้พื้นที่เพียงบิตเดียวแทนที่จะเป็นหนึ่งไบต์ตามที่บูลปกติจะทำ (การใช้งานประเภทของ "บิตเซ็ตแบบไดนามิก") เพื่อแลกกับการเพิ่มประสิทธิภาพนี้ไม่ได้นำเสนอความสามารถและอินเทอร์เฟซทั้งหมดของคอนเทนเนอร์มาตรฐานทั่วไป

ในกรณีนี้เนื่องจากคุณไม่สามารถใช้ที่อยู่ของบิตภายในหนึ่งไบต์สิ่งต่างๆเช่นoperator[]ไม่สามารถส่งคืน a bool&แต่ส่งคืนวัตถุพร็อกซีที่อนุญาตให้จัดการบิตที่เป็นปัญหาแทน เนื่องจากอ็อบเจ็กต์พร็อกซีนี้ไม่ใช่ a bool&คุณจึงไม่สามารถกำหนดที่อยู่ของมันให้bool*เหมือนที่คุณทำได้ด้วยผลลัพธ์ของโอเปอเรเตอร์ดังกล่าวเรียกคอนเทนเนอร์ "ปกติ" ซึ่งหมายความว่านั่นbool *pb =&v[0];ไม่ใช่รหัสที่ถูกต้อง

ในทางกลับกันdequeไม่มีความเชี่ยวชาญพิเศษใด ๆ ที่เรียกออกมาดังนั้นแต่ละบูลจะใช้เวลาหนึ่งไบต์และคุณสามารถใช้ที่อยู่ของการคืนค่าoperator[]ได้

สุดท้ายโปรดทราบว่าการใช้งานไลบรารีมาตรฐาน MS เป็น (เนื้อหา) ที่ไม่เหมาะสมเนื่องจากใช้ขนาดชิ้นเล็ก ๆ สำหรับ deques ซึ่งหมายความว่าการใช้ deque เป็นสิ่งทดแทนไม่ใช่คำตอบที่ถูกต้องเสมอไป


5
เรามีข้อมูลประเภทอื่นที่คอนเทนเนอร์ STL อื่นใดเป็นแบบพิเศษหรือเรียกอย่างชัดเจน
P0W

3
สิ่งนี้ใช้กับ C ++ 11 std :: array <bool> หรือไม่
Sergio Basurco

4
@chuckleplant no std::arrayเป็นเพียงเครื่องห่อหุ้มเทมเพลตรอบ ๆ อาร์เรย์ดิบที่T[n]มีฟังก์ชันตัวช่วยบางอย่างเช่นsize()คัดลอก / ย้ายความหมายและตัวทำซ้ำเพิ่มเพื่อให้เข้ากันได้กับ STL และ (ขอบคุณ) มันไม่ได้ละเมิดหลักการของตัวเองเพื่อ (สังเกตของฉัน ความสงสัยเหล่านี้ :) 'เชี่ยวชาญ' สำหรับ ' bool'
underscore_d

เพียงแค่เลือก nit - sizeof (bool) ไม่จำเป็นต้องเป็นไบต์ stackoverflow.com/questions/4897844/…
Uri Raz

30

vector<bool>มีค่าบูลีนในรูปแบบบีบอัดโดยใช้เพียงบิตเดียวสำหรับค่า (ไม่ใช่ 8 วิธีการทำงานของอาร์เรย์ bool []) เป็นไปไม่ได้ที่จะส่งคืนการอ้างอิงไปยังบิตใน c ++ ดังนั้นจึงมีตัวช่วยพิเศษประเภท "การอ้างอิงบิต" ซึ่งให้อินเทอร์เฟซกับบิตในหน่วยความจำและอนุญาตให้คุณใช้ตัวดำเนินการและแคสต์มาตรฐาน


1
@PrashantSrivastava deque<bool>ไม่เชี่ยวชาญดังนั้นมันจึงเป็นเพียงบูลถือ deque เท่านั้น
Konrad Rudolph

@PrashantSrivastava vector<bool>มีการใช้งานเทมเพลตเฉพาะ ฉันเดาว่าคอนเทนเนอร์ STL อื่น ๆ เช่นdeque<bool>ไม่ดังนั้นพวกเขาจึงถือ bool-s เหมือนประเภทอื่น ๆ
Ivan Smirnov

นี่คือคำถามที่ถามสิ่งที่คล้ายกันในสนิมที่พวกเขาไม่อนุญาตบูลีนบิตเดียวstackoverflow.com/questions/48875251/…
andy boot

25

ปัญหาคือvector<bool>ส่งคืนวัตถุอ้างอิงพร็อกซีแทนที่จะเป็นการอ้างอิงจริงดังนั้นโค้ดสไตล์ C ++ 98 bool * p = &v[0];จะไม่คอมไพล์ อย่างไรก็ตามที่ทันสมัย C ++ 11 auto p = &v[0];สามารถทำเพื่อรวบรวมถ้าoperator&ยังส่งกลับวัตถุชี้พร็อกซี่ Howard Hinnant ได้เขียนบล็อกโพสต์เกี่ยวกับการปรับปรุงอัลกอริทึมเมื่อใช้การอ้างอิงและคำแนะนำพร็อกซีดังกล่าว

Scott Meyers มี Item 30 ยาวในC ++ ที่มีประสิทธิภาพมากกว่าเกี่ยวกับคลาสพร็อกซี คุณสามารถมาทางยาวเกือบเลียนแบบประเภท builtin นี้สำหรับประเภทใดก็ตามTคู่ผู้รับมอบฉันทะ (เช่นreference_proxy<T>และiterator_proxy<T>) สามารถทำร่วมกันที่สอดคล้องกันในแง่ที่ว่าreference_proxy<T>::operator&()และiterator_proxy<T>::operator*()มีความผกผันของกันและกัน

อย่างไรก็ตามในบางจุดหนึ่งต้อง map วัตถุพร็อกซี่กลับไปทำตัวเหมือนหรือT* T&สำหรับพร็อกซีตัววนซ้ำเราสามารถโอเวอร์โหลดoperator->()และเข้าถึงTอินเทอร์เฟซของเทมเพลตได้โดยไม่ต้องใช้ฟังก์ชันทั้งหมดซ้ำ อย่างไรก็ตามสำหรับพร็อกซีอ้างอิงคุณจะต้องโอเวอร์โหลดoperator.()และไม่ได้รับอนุญาตใน C ++ ปัจจุบัน (แม้ว่า Sebastian Redl จะนำเสนอข้อเสนอดังกล่าวใน BoostCon 2013) คุณสามารถทำการแก้ไขอย่างละเอียดเหมือน.get()สมาชิกภายในพร็อกซีอ้างอิงหรือใช้Tอินเทอร์เฟซทั้งหมดภายในการอ้างอิง (นี่คือสิ่งที่ทำเพื่อvector<bool>::bit_reference) แต่สิ่งนี้จะสูญเสียไวยากรณ์ในตัวหรือแนะนำการแปลงที่ผู้ใช้กำหนดเองที่ไม่มีความหมายในตัวสำหรับการแปลงประเภท (คุณสามารถมี Conversion ที่ผู้ใช้กำหนดได้สูงสุดหนึ่งรายการต่อหนึ่งอาร์กิวเมนต์)

TL; DR : vector<bool>ไม่ใช่ไม่ใช่คอนเทนเนอร์เนื่องจาก Standard ต้องการการอ้างอิงจริง แต่สามารถทำให้ทำงานได้เกือบเหมือนคอนเทนเนอร์อย่างน้อยก็ใกล้เคียงกับ C ++ 11 (อัตโนมัติ) มากกว่าใน C ++ 98


10

หลายคนมองว่าความvector<bool>เชี่ยวชาญเป็นความผิดพลาด

ในกระดาษ"Deprecating Vestigial Library Parts in C ++ 17"
มีข้อเสนอให้ พิจารณาความเชี่ยวชาญบางส่วนของเวกเตอร์อีกครั้ง

มีประวัติอันยาวนานเกี่ยวกับความเชี่ยวชาญเฉพาะบางส่วนของบูลของ std :: vector ที่ไม่เป็นไปตามข้อกำหนดของคอนเทนเนอร์และโดยเฉพาะอย่างยิ่งตัวทำซ้ำไม่เป็นไปตามข้อกำหนดของตัววนซ้ำการเข้าถึงแบบสุ่ม ความพยายามก่อนหน้านี้ที่จะเลิกใช้ภาชนะถูกปฏิเสธสำหรับ C ++ 11 N2204


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


5

ดูวิธีการใช้งาน STL สร้างขึ้นอย่างมากมายบนเทมเพลตดังนั้นส่วนหัวจึงมีโค้ดที่พวกเขาทำ

เพื่อให้ดูตัวอย่างที่stdc ++การดำเนินงานที่นี่

ยังน่าสนใจแม้ว่าจะไม่ได้เป็นไปตามกลไกการ STL เวกเตอร์บิตเป็นLLVM :: BitVectorจากที่นี่

สาระสำคัญของllvm::BitVectorคลาสที่ซ้อนกันเรียกว่าreferenceและตัวดำเนินการที่เหมาะสมมากเกินไปเพื่อให้BitVectorพฤติกรรมคล้ายvectorกับข้อ จำกัด บางประการ โค้ดด้านล่างเป็นอินเทอร์เฟซที่เรียบง่ายเพื่อแสดงให้เห็นว่า BitVector ซ่อนคลาสที่เรียกว่าreferenceการใช้งานจริงเกือบจะทำงานเหมือนบูลอาร์เรย์จริงโดยไม่ต้องใช้ 1 ไบต์สำหรับแต่ละค่า

class BitVector {
public:
  class reference {
    reference &operator=(reference t);
    reference& operator=(bool t);
    operator bool() const;
  };
  reference operator[](unsigned Idx);
  bool operator[](unsigned Idx) const;      
};

รหัสนี้มีคุณสมบัติที่ดี:

BitVector b(10, false); // size 10, default false
BitVector::reference &x = b[5]; // that's what really happens
bool y = b[5]; // implicitly converted to bool 
assert(b[5] == false); // converted to bool
assert(b[6] == b[7]); // bool operator==(const reference &, const reference &);
b[5] = true; // assignment on reference
assert(b[5] == true); // and actually it does work.

รหัสนี้มีข้อบกพร่องจริงลองเรียกใช้:

std::for_each(&b[5], &b[6], some_func); // address of reference not an iterator

จะไม่ทำงานเพราะassert( (&b[5] - &b[3]) == (5 - 3) );จะล้มเหลว (ภายในllvm::BitVector)

นี่คือเวอร์ชัน llvm ที่เรียบง่ายมาก std::vector<bool>ยังมีตัวทำซ้ำที่ใช้งานได้อีกด้วย ดังนั้นการโทรfor(auto i = b.begin(), e = b.end(); i != e; ++i)จะทำงาน std::vector<bool>::const_iteratorและนอกจากนี้ยังมี

อย่างไรก็ตามยังมีข้อ จำกัดstd::vector<bool>ที่ทำให้มันทำงานแตกต่างกันในบางกรณี


3

มาจากhttp://www.cplusplus.com/reference/vector/vector-bool/

Vector of bool นี่คือเวกเตอร์เวอร์ชันพิเศษซึ่งใช้สำหรับองค์ประกอบประเภทบูลและปรับให้เหมาะสมกับพื้นที่

มันทำงานเหมือนเวกเตอร์เวอร์ชันที่ไม่เฉพาะเจาะจงโดยมีการเปลี่ยนแปลงดังต่อไปนี้:

  • หน่วยเก็บข้อมูลไม่จำเป็นต้องเป็นอาร์เรย์ของค่าบูล แต่การใช้งานไลบรารีอาจเพิ่มประสิทธิภาพการจัดเก็บเพื่อให้แต่ละค่าถูก
    เก็บไว้ในบิตเดียว
  • องค์ประกอบไม่ได้สร้างโดยใช้ออบเจ็กต์ตัวจัดสรร แต่ค่าของมันถูกตั้งค่าโดยตรงบนบิตที่เหมาะสมในที่เก็บข้อมูลภายใน
  • ฟังก์ชันสมาชิกพลิกและลายเซ็นใหม่สำหรับการแลกเปลี่ยนสมาชิก
  • ประเภทสมาชิกพิเศษการอ้างอิงคลาสที่เข้าถึงแต่ละบิตในที่เก็บข้อมูลภายในของคอนเทนเนอร์ด้วยอินเทอร์เฟซที่
    เลียนแบบการอ้างอิงบูล ในทางกลับกันประเภทสมาชิก const_reference เป็นบูลธรรมดา
  • ประเภทตัวชี้และตัววนซ้ำที่ใช้โดยคอนเทนเนอร์ไม่จำเป็นต้องเป็นตัวชี้หรือตัวทำซ้ำที่สอดคล้องกันแม้ว่า
    จะจำลองพฤติกรรมที่คาดหวังไว้เกือบทั้งหมดก็ตาม

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

bitset เป็นคลาสที่มีฟังก์ชันการทำงานที่คล้ายกันสำหรับอาร์เรย์ขนาดคงที่ของบิต


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