ฉันจะเลือกคอนเทนเนอร์ไลบรารีมาตรฐานใน C ++ 11 อย่างมีประสิทธิภาพได้อย่างไร


136

มีรูปภาพที่รู้จักกันดี (ข้อมูลโกง) ที่เรียกว่า "C ++ Container choice" เป็นผังงานเพื่อเลือกภาชนะที่ดีที่สุดสำหรับการใช้งานที่ต้องการ

มีใครรู้บ้างว่ามีเวอร์ชัน C ++ 11 อยู่แล้วหรือไม่?

นี่คือรายการก่อนหน้า: ตัวเลือกคอนเทนเนอร์ eC ++


6
ไม่เคยเห็นมาก่อน ขอบคุณ!
WeaselFox

6
@WeaselFox: มันเป็นส่วนหนึ่งของC ++ อยู่แล้ว - คำถามที่พบบ่อยเกี่ยวกับ SO
Alok Save

4
C ++ 11 แนะนำประเภทคอนเทนเนอร์จริงใหม่เท่านั้น: คอนเทนเนอร์ unordered_X การรวมถึงพวกเขาจะทำให้โต๊ะยุ่งเหยิงมากเท่านั้นเนื่องจากมีข้อควรพิจารณาหลายประการในการตัดสินใจว่าตารางแฮชนั้นเหมาะสมหรือไม่
Nicol Bolas

13
เจมส์พูดถูกมีหลายกรณีที่ต้องใช้เวกเตอร์มากกว่าที่ตารางแสดง ข้อได้เปรียบของตำแหน่งข้อมูลมีประสิทธิภาพสูงกว่าในหลาย ๆ กรณีคือการขาดประสิทธิภาพในการดำเนินการบางอย่าง (เร็ว ๆ นี้ C ++ 11) ฉันไม่พบว่าแผนภูมิ e มีประโยชน์สำหรับ c ++ 03
David Rodríguez - dribeas

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

คำตอบ:


97

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

ในการสร้างแผนภูมิดังกล่าวคุณต้องมีแนวทางง่ายๆสองข้อ:

  • เลือกสำหรับความหมายก่อน
  • เมื่อมีทางเลือกมากมายให้เลือกวิธีที่ง่ายที่สุด

การกังวลเกี่ยวกับประสิทธิภาพมักจะไร้ประโยชน์ในตอนแรก การพิจารณา O ครั้งใหญ่จะเกิดขึ้นเมื่อคุณเริ่มจัดการรายการไม่กี่พัน (หรือมากกว่า) เท่านั้น

ตู้คอนเทนเนอร์มีสองประเภทใหญ่ ๆ :

  • คอนเทนเนอร์ที่เกี่ยวข้อง : มีการfindดำเนินการ
  • คอนเทนเนอร์ลำดับอย่างง่าย

stackและจากนั้นคุณสามารถสร้างอะแดปเตอร์หลายด้านบนของพวกเขา queue, priority_queue, ฉันจะทิ้งอะแดปเตอร์ไว้ที่นี่เนื่องจากมีความเชี่ยวชาญเพียงพอที่จะจดจำได้


คำถามที่ 1: Associative ?

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

คำถาม 1.1: สั่งซื้อ ?

  • หากคุณไม่ต้องการคำสั่งซื้อเฉพาะให้ใช้unordered_คอนเทนเนอร์หรือใช้คำสั่งซื้อแบบดั้งเดิม

คำถาม 1.2: แยกคีย์ ?

  • หากคีย์แยกจากค่าให้ใช้ a mapหรือใช้ aset

คำถามที่ 1.3: ซ้ำกัน ?

  • หากคุณต้องการเก็บข้อมูลที่ซ้ำกันให้ใช้ a multiมิฉะนั้นอย่าทำ

ตัวอย่าง:

สมมติว่าฉันมีบุคคลหลายคนที่มี ID เฉพาะที่เกี่ยวข้องกับพวกเขาและฉันต้องการดึงข้อมูลบุคคลจาก ID ของบุคคลนั้นให้ง่ายที่สุด

  1. ฉันต้องการfindฟังก์ชันจึงเป็นคอนเทนเนอร์ที่เชื่อมโยงกัน

    1.1. ฉันไม่สนใจคำสั่งซื้อน้อยลงดังนั้นunordered_คอนเทนเนอร์

    1.2. คีย์ (ID) ของฉันแยกจากค่าที่เกี่ยวข้องดังนั้นจึงเป็นmap

    1.3. ID ไม่ซ้ำกันจึงไม่ควรเล็ดลอดเข้ามาซ้ำ

คำตอบสุดท้ายคือ: std::unordered_map<ID, PersonData>.


คำถามที่ 2: หน่วยความจำเสถียรหรือไม่?

  • หากองค์ประกอบควรมีความเสถียรในหน่วยความจำ (กล่าวคือไม่ควรเคลื่อนที่ไปมาเมื่อมีการปรับเปลี่ยนคอนเทนเนอร์เอง) ให้ใช้บางส่วน list
  • มิฉะนั้นข้ามไปที่คำถามที่ 3

คำถาม 2.1: ไหน ?

  • ตั้งถิ่นฐานเพื่อlist; a forward_listมีประโยชน์สำหรับหน่วยความจำที่น้อยลงเท่านั้น

คำถามที่ 3: ขนาดแบบไดนามิก ?

  • หากคอนเทนเนอร์มีขนาดที่ทราบ (ในเวลาคอมไพล์) และขนาดนี้จะไม่ถูกเปลี่ยนแปลงในระหว่างการทำงานของโปรแกรมและองค์ประกอบต่างๆเป็นค่าเริ่มต้นที่สร้างได้หรือคุณสามารถจัดเตรียมรายการเริ่มต้นทั้งหมด (โดยใช้{ ... }ไวยากรณ์) จากนั้นใช้array. แทนที่ C-array แบบเดิม แต่มีฟังก์ชันที่สะดวก
  • มิฉะนั้นให้ข้ามไปที่คำถาม 4

คำถามที่ 4: Double-ended ?

  • หากคุณต้องการนำรายการออกจากทั้งด้านหน้าและด้านหลังให้ใช้ a dequeหรือใช้ a vector.

vectorคุณจะทราบว่าโดยปกติถ้าคุณต้องการภาชนะที่สมาคมทางเลือกของคุณจะเป็น มันจะเปิดออกก็ยังเป็นซัทเทอและ Stroustrup ของคำแนะนำ


5
+1 แต่มีหมายเหตุ: 1) arrayไม่ต้องการประเภทที่สร้างได้เริ่มต้น 2) การเลือกรายการmultiนั้นไม่มากเกี่ยวกับการอนุญาตให้ทำซ้ำแต่ข้อมูลเพิ่มเติมเกี่ยวกับว่าการเก็บข้อมูลเหล่านี้มีความสำคัญหรือไม่ (คุณสามารถใส่รายการที่ซ้ำกันในmultiคอนเทนเนอร์ที่ไม่ใช่คอนเทนเนอร์ได้ แต่ก็เกิดขึ้นที่มีเพียงรายการเดียวเท่านั้นที่ถูกเก็บไว้)
R.Martinho Fernandes

2
ตัวอย่างหลุดไปหน่อย 1) เราสามารถ "ค้นหา" (ไม่ใช่ฟังก์ชันสมาชิกซึ่งเป็นฟังก์ชัน "<อัลกอริทึม>") บนคอนเทนเนอร์ที่ไม่เชื่อมโยง 1.1) หากเราต้องการหา "ประสิทธิภาพ" และ unordered_ จะเป็น O (1) และไม่ใช่ O ( ล็อก n)
BlakBat

4
@BlakBat: map.find(key)เป็นที่พอใจมากขึ้นกว่าstd::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));แต่ดังนั้นจึงเป็นสิ่งที่สำคัญความหมายว่าเป็นหน้าที่ของสมาชิกมากกว่าหนึ่งจากfind <algorithm>สำหรับ O (1) กับ O (log n) จะไม่มีผลต่อความหมาย ฉันจะลบ "ประสิทธิภาพ" ออกจากตัวอย่างและแทนที่ด้วย "อย่างง่ายดาย"
Matthieu M.

"ถ้าองค์ประกอบควรมีความเสถียรในหน่วยความจำ ... ก็ใช้รายการ" ... อืมฉันคิดว่าdequeมีคุณสมบัตินี้ด้วยเหรอ?
Martin Ba

@MartinBa: ใช่และไม่ใช่ ในdequeองค์ประกอบจะคงที่ก็ต่อเมื่อคุณดัน / ป๊อปที่ปลายด้านใดด้านหนึ่ง หากคุณเริ่มแทรก / ลบตรงกลางองค์ประกอบสูงสุด N / 2 จะถูกสับเพื่อเติมช่องว่างที่สร้างขึ้น
Matthieu M.

51

ฉันชอบคำตอบของ Matthieu แต่ฉันจะสร้างผังงานใหม่เป็นดังนี้:

เมื่อใดที่ไม่ควรใช้ std :: vector

std::vectorโดยค่าเริ่มต้นถ้าคุณต้องการภาชนะของสิ่งที่ใช้ ดังนั้นคอนเทนเนอร์อื่น ๆ ทั้งหมดจึงมีเหตุผลโดยการให้ฟังก์ชันทางเลือกบางอย่างstd::vectorเท่านั้น

ตัวสร้าง

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

A std::dequeจะเป็นการแทนที่ทั่วไปโดยมีคุณสมบัติมากมายstd::vectorแต่คุณสามารถแทรกที่ปลายด้านใดด้านหนึ่งของ deque เท่านั้น เม็ดมีดตรงกลางต้องการการเคลื่อนย้าย std::listสถานที่ต้องการไม่มีในเนื้อหา

ต้องการ Bools

std::vector<bool>ไม่ใช่. มันเป็นมาตรฐาน แต่มันไม่ใช่vectorในแง่ปกติเนื่องจากการดำเนินการที่std::vectorอนุญาตตามปกติเป็นสิ่งต้องห้าม และแน่นอนที่สุดไม่ได้มีbool s

ดังนั้นถ้าคุณต้องการจริงvectorพฤติกรรมจากภาชนะของbools std::vector<bool>คุณจะไม่ได้รับจาก ดังนั้นคุณจะต้องชำระเงินด้วยไฟล์std::deque<bool>.

กำลังค้นหา

หากคุณต้องการที่จะหาองค์ประกอบในภาชนะและแท็กค้นหาไม่สามารถเพียงแค่เป็นดัชนีแล้วคุณอาจต้องละทิ้งstd::vectorในความโปรดปรานของและset mapสังเกตคำสำคัญ " may "; std::vectorบางครั้งการเรียงลำดับก็เป็นทางเลือกที่สมเหตุสมผล หรือ Boost.Container ของซึ่งการดำเนินการเรียงลำดับflat_set/mapstd::vector

ตอนนี้มีสี่รูปแบบเหล่านี้แต่ละแบบมีความต้องการของตัวเอง

  • ใช้mapเมื่อแท็กค้นหาไม่ใช่สิ่งเดียวกับรายการที่คุณกำลังค้นหา มิฉะนั้นให้ใช้ไฟล์set.
  • ใช้unorderedเมื่อคุณมีจำนวนมากของรายการในภาชนะและค้นหาประสิทธิภาพอย่างจะต้องมีมากกว่าO(1)O(logn)
  • ใช้multiหากคุณต้องการหลายรายการเพื่อให้มีแท็กการค้นหาเดียวกัน

การสั่งซื้อ

หากคุณต้องการให้มีการจัดเรียงตู้สินค้าตามการดำเนินการเปรียบเทียบโดยเฉพาะคุณสามารถใช้ไฟล์set. หรือmulti_setถ้าคุณต้องการหลายรายการเพื่อให้มีมูลค่าเท่ากัน

หรือคุณสามารถใช้การจัดเรียงstd::vectorแต่คุณจะต้องจัดเรียงไว้

เสถียรภาพ

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

std::listเสนอการรับประกันที่มั่นคง: ตัววนซ้ำและการอ้างอิง / ตัวชี้ที่เกี่ยวข้องจะไม่ถูกต้องก็ต่อเมื่อมีการนำไอเท็มออกจากคอนเทนเนอร์ std::forward_listจะมีไหมหากความทรงจำเป็นปัญหาร้ายแรง

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

ประสิทธิภาพการแทรก

std::vector ให้การแทรกราคาถูกในตอนท้ายเท่านั้น (และถึงแม้จะมีราคาแพงหากคุณเป่าความจุ)

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

std::dequeให้การแทรก / ถอดที่หัวและท้ายตลอดเวลา แต่การแทรกตรงกลางอาจมีราคาแพงพอสมควร ดังนั้นหากคุณต้องการเพิ่ม / ลบสิ่งต่างๆจากด้านหน้าและด้านหลังstd::dequeอาจเป็นสิ่งที่คุณต้องการ

ควรสังเกตว่าด้วยการย้ายความหมายstd::vectorประสิทธิภาพการแทรกอาจไม่เลวร้ายอย่างที่เคยเป็น การใช้งานบางอย่างใช้รูปแบบของการคัดลอกรายการที่อิงตามความหมาย (หรือที่เรียกว่า "swaptimization") แต่ตอนนี้การย้ายนั้นเป็นส่วนหนึ่งของภาษามันได้รับคำสั่งจากมาตรฐาน

ไม่มีการจัดสรรแบบไดนามิก

std::arrayเป็นคอนเทนเนอร์ที่ดีหากคุณต้องการการจัดสรรแบบไดนามิกน้อยที่สุดเท่าที่จะเป็นไปได้ มันเป็นเพียงกระดาษห่อหุ้มรอบ C-array ที่นี้หมายถึงว่าขนาดของมันจะต้องรู้จักที่รวบรวมเวลา std::arrayหากคุณสามารถอยู่กับที่แล้วใช้

ที่ถูกกล่าวว่าการใช้std::vectorและreserveไอเอ็นจีขนาดจะทำงานได้เป็นอย่างดีสำหรับ std::vectorbounded ด้วยวิธีนี้ขนาดจริงอาจแตกต่างกันไปและคุณจะได้รับการจัดสรรหน่วยความจำเพียงชุดเดียว (เว้นแต่คุณจะเพิ่มขีดความสามารถ)


1
ฉันชอบคำตอบของคุณมากเช่นกัน :) WRT เก็บเวกเตอร์ที่เรียงลำดับไว้นอกจากstd::sortนี้ยังมีสิ่งstd::inplace_mergeที่น่าสนใจในการวางองค์ประกอบใหม่ ๆ ได้อย่างง่ายดาย (แทนที่จะเป็นstd::lower_bound+ std::vector::insertโทร) ยินดีที่ได้เรียนรู้flat_setและflat_map!
Matthieu M.

2
คุณยังไม่สามารถใช้เวกเตอร์ที่มีประเภทการจัดชิด 16 ไบต์ นอกจากนี้ยังมีการเปลี่ยนที่ดีสำหรับการเป็นvector<bool> vector<char>
ผกผัน

@Inverse: "นอกจากนี้คุณยังไม่สามารถใช้เวกเตอร์ที่มีประเภทการจัดชิด 16 ไบต์" บอกว่าใคร? หากstd::allocator<T>ไม่รองรับการจัดแนวนั้น (และฉันไม่รู้ว่าทำไมจึงไม่) คุณสามารถใช้ตัวจัดสรรที่กำหนดเองได้ตลอดเวลา
Nicol Bolas

2
@ Inverse: C ++ 11 std::vector::resizeมีโอเวอร์โหลดที่ไม่ใช้ค่า (ใช้ขนาดใหม่เท่านั้นองค์ประกอบใหม่ใด ๆ จะถูกสร้างขึ้นตามค่าเริ่มต้น) นอกจากนี้เหตุใดคอมไพเลอร์จึงไม่สามารถจัดแนวพารามิเตอร์ค่าได้อย่างถูกต้องแม้ว่าจะมีการประกาศว่ามีการจัดแนวก็ตาม
Nicol Bolas

1
bitsetสำหรับบูลหากคุณทราบขนาดล่วงหน้าen.cppreference.com/w/cpp/utility/bitset
bendervader

25

นี่คือผังงานด้านบนเวอร์ชัน C ++ 11 [โพสต์ครั้งแรกโดยไม่มีการระบุแหล่งที่มาของผู้เขียนต้นฉบับMikael Persson ]


2
@NO_NAME ว้าวฉันดีใจที่มีคนรบกวนการอ้างอิงแหล่งที่มา
underscore_d

1

นี่เป็นการหมุนอย่างรวดเร็วแม้ว่าอาจต้องใช้งาน

Should the container let you manage the order of the elements?
Yes:
  Will the container contain always exactly the same number of elements? 
  Yes:
    Does the container need a fast move operator?
    Yes: std::vector
    No: std::array
  No:
    Do you absolutely need stable iterators? (be certain!)
    Yes: boost::stable_vector (as a last case fallback, std::list)
    No: 
      Do inserts happen only at the ends?
      Yes: std::deque
      No: std::vector
No: 
  Are keys associated with Values?
  Yes:
    Do the keys need to be sorted?
    Yes: 
      Are there more than one value per key?
      Yes: boost::flat_map (as a last case fallback, std::map)
      No: boost::flat_multimap (as a last case fallback, std::map)
    No:
      Are there more than one value per key?
      Yes: std::unordered_multimap
      No: std::unordered_map
  No:
    Are elements read then removed in a certain order?
    Yes:
      Order is:
      Ordered by element: std::priority_queue
      First in First out: std::queue
      First in Last out: std::stack
      Other: Custom based on std::vector????? 
    No:
      Should the elements be sorted by value?
      Yes: boost::flat_set
      No: std::vector

คุณอาจพบว่ามีความแตกต่างอย่างดุเดือดจากซี ++ 03 รุ่นหลักเนื่องจากความจริงที่ว่าผมไม่ชอบโหนดที่เชื่อมโยง โดยปกติคอนเทนเนอร์โหนดที่เชื่อมโยงสามารถเอาชนะประสิทธิภาพได้โดยคอนเทนเนอร์ที่ไม่ได้เชื่อมโยงยกเว้นในบางสถานการณ์ที่เกิดขึ้นได้ยาก หากคุณไม่รู้ว่าสถานการณ์เหล่านั้นคืออะไรและสามารถเข้าถึงบูสต์ได้อย่าใช้คอนเทนเนอร์โหนดที่เชื่อมโยง (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset) รายการนี้เน้นที่คอนเทนเนอร์ขนาดเล็กและขนาดกลางเป็นส่วนใหญ่เนื่องจาก (A) เป็น 99.99% ของสิ่งที่เราจัดการในโค้ดและ (B) องค์ประกอบจำนวนมากต้องการอัลกอริทึมที่กำหนดเองไม่ใช่คอนเทนเนอร์ที่แตกต่าง

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