ความจุเริ่มต้นของเวกเตอร์ใน C ++


92

อะไรคือสิ่งที่capacity()ของstd::vectorซึ่งถูกสร้างขึ้นโดยใช้ constuctor เริ่มต้น? ฉันรู้ว่าค่าsize()เป็นศูนย์ เราสามารถระบุว่าเวกเตอร์ที่สร้างขึ้นเริ่มต้นไม่เรียกการจัดสรรหน่วยความจำฮีปได้หรือไม่?

std::vector<int> iv; iv.reserve(2345);วิธีนี้มันจะเป็นไปได้ที่จะสร้างอาร์เรย์ที่มีสำรองโดยพลการโดยใช้การจัดสรรเดียวเช่น สมมติว่าด้วยเหตุผลบางประการฉันไม่ต้องการเริ่มsize()วันที่ 2345

ตัวอย่างเช่นบน Linux (g ++ 4.4.5, kernel 2.6.32 amd64)

#include <iostream>
#include <vector>

int main()
{
  using namespace std;
  cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
  return 0;
}

0,10ถูกพิมพ์ เป็นกฎหรือไม่หรือผู้ขาย STL ขึ้นอยู่กับ?


7
Standard ไม่ได้ระบุอะไรเกี่ยวกับความจุเริ่มต้นของเวกเตอร์ แต่การนำไปใช้งานส่วนใหญ่ใช้ 0
Mr. Anubis

11
ไม่มีการรับประกัน แต่ฉันจะถามอย่างจริงจังถึงคุณภาพของการใช้งานใด ๆ ที่จัดสรรหน่วยความจำโดยที่ฉันไม่ร้องขอใด ๆ
Mike Seymour

2
@MikeSeymour ไม่เห็นด้วย การใช้งานที่มีประสิทธิภาพสูงมากอาจมีบัฟเฟอร์แบบอินไลน์ขนาดเล็กซึ่งในกรณีนี้การตั้งค่าความจุเริ่มต้น () เป็นสิ่งที่สมเหตุสมผล
alastair

6
@alastair เมื่อใช้ตัวswapวนซ้ำและการอ้างอิงทั้งหมดยังคงถูกต้อง (ยกเว้นend()s) นั่นหมายความว่าไม่สามารถใช้บัฟเฟอร์แบบอินไลน์ได้
Notinlist

คำตอบ:


74

มาตรฐานไม่ได้ระบุว่าจุดเริ่มต้นcapacityของคอนเทนเนอร์ควรเป็นอย่างไรดังนั้นคุณจึงต้องอาศัยการนำไปใช้ การใช้งานทั่วไปจะเริ่มความจุที่ศูนย์ แต่ไม่มีการรับประกัน ในทางกลับกันไม่มีวิธีใดที่จะทำให้กลยุทธ์ของคุณstd::vector<int> iv; iv.reserve(2345);ยึดติดกับมันได้ดีขึ้น


1
ฉันไม่ซื้องบสุดท้ายของคุณ หากคุณไม่สามารถพึ่งพาความจุเป็น 0 ได้ในตอนแรกคุณอาจปรับโครงสร้างโปรแกรมของคุณใหม่เพื่อให้เวกเตอร์ของคุณมีขนาดเริ่มต้น นี่จะเป็นครึ่งหนึ่งของจำนวนคำขอหน่วยความจำฮีป (จาก 2 เป็น 1)
bitmask

4
@bitmask: การใช้งานจริง: คุณรู้หรือไม่ว่ามีการใช้งานที่เวกเตอร์จัดสรรหน่วยความจำในตัวสร้างเริ่มต้นหรือไม่? มันไม่ได้การันตีโดยมาตรฐาน แต่ขณะที่ไมค์มัวร์ชี้ให้เห็นวิกฤติการจัดสรรโดยไม่จำเป็นต้องจะเป็นกลิ่นที่ไม่ดีเกี่ยวกับคุณภาพของการดำเนินงาน
David Rodríguez - dribeas

3
@ DavidRodríguez-dribeas: นั่นไม่ใช่ประเด็น หลักฐานคือ "คุณไม่สามารถทำได้ดีไปกว่ากลยุทธ์ปัจจุบันของคุณดังนั้นอย่ากังวลว่าจะมีการใช้งานที่โง่เขลาหรือไม่" หากหลักฐานคือ "ไม่มีการนำไปใช้งานดังนั้นไม่ต้องกังวล" ฉันจะซื้อมัน ข้อสรุปเกิดขึ้นเป็นจริง แต่ความหมายไม่ได้ผล ขอโทษทีฉันอาจจะเลือกไม่ถูก
bitmask

3
@bitmask หากมีการใช้งานที่จัดสรรหน่วยความจำในโครงสร้างเริ่มต้นการทำในสิ่งที่คุณบอกจะลดจำนวนการจัดสรรลงครึ่งหนึ่ง แต่vector::reserveไม่เหมือนกับการระบุขนาดเริ่มต้น ตัวสร้างเวกเตอร์ที่รับค่าขนาดเริ่มต้น / คัดลอกเริ่มต้นnวัตถุจึงมีความซับซ้อนเชิงเส้น OTOH การโทรสำรองหมายถึงการคัดลอก / ย้ายsize()องค์ประกอบเท่านั้นหากมีการเรียกใช้การจัดสรรใหม่ บนเวกเตอร์ว่างเปล่าไม่มีอะไรให้คัดลอก ดังนั้นสิ่งหลังอาจเป็นที่ต้องการแม้ว่าการใช้งานจะจัดสรรหน่วยความจำสำหรับเวกเตอร์ที่สร้างขึ้นเริ่มต้น
Praetorian

4
@bitmask หากคุณกังวลเกี่ยวกับการจัดสรรในระดับนี้คุณควรดูการใช้งานไลบรารีมาตรฐานเฉพาะของคุณและไม่ต้องพึ่งพาการคาดเดา
Mark Ransom

36

การใช้งานสตอเรจของ std :: vector นั้นแตกต่างกันอย่างมาก แต่สิ่งที่ฉันเจอทั้งหมดเริ่มต้นจาก 0

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

#include <iostream>
#include <vector>

int main()
{
  using namespace std;

  vector<int> normal;
  cout << normal.capacity() << endl;

  for (unsigned int loop = 0; loop != 10; ++loop)
  {
      normal.push_back(1);
      cout << normal.capacity() << endl;
  }

  cin.get();
  return 0;
}

ให้ผลลัพธ์ต่อไปนี้:

0
1
2
4
4
8
8
8
8
16
16

ภายใต้ GCC 5.1 และ:

0
1
2
3
4
6
6
9
9
9
13

ภายใต้ MSVC 2013


3
@Andrew
Valentin Mercier

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

@ แอนดรูว์สิ่งที่พวกเขาควรจะเริ่มต้นที่? การจัดสรรอะไรก็จะเสียเวลาในการจัดสรรและยกเลิกการจัดสรรหน่วยความจำนั้นหากโปรแกรมเมอร์ต้องการสำรองมากกว่าค่าเริ่มต้น หากคุณสมมติว่าควรเริ่มต้นด้วย 1 มันจะจัดสรรสิ่งนั้นทันทีที่มีคนจัดสรร 1 ต่อไป
Puddle

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

@ แอนดรูโอ้ดีคุณโล่งใจมากพอพวกเขาเริ่มที่ 0 ทำไมถึงแสดงความคิดเห็นเกี่ยวกับเรื่องนี้ในทางติดตลก?
Puddle

7

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

  • constructor เพื่อสร้างคอนเทนเนอร์เอง
  • reserve() เพื่อจัดสรรบล็อกหน่วยความจำขนาดใหญ่ที่เหมาะสมไว้ล่วงหน้าเพื่อรองรับวัตถุจำนวนอย่างน้อย (!)

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

ดังนั้นรวมทั้งหมดเข้าด้วยกัน:

  • โดยเจตนามาตรฐานไม่ได้ระบุตัวสร้างที่ช่วยให้คุณสามารถจัดสรรบล็อกหน่วยความจำล่วงหน้าสำหรับจำนวนออบเจ็กต์ที่เฉพาะเจาะจง (ซึ่งอย่างน้อยก็น่าจะดีกว่าการจัดสรรการใช้งานที่เฉพาะเจาะจง "บางสิ่ง" ที่คงที่ภายใต้ประทุน)
  • การจัดสรรไม่ควรเป็นนัย ดังนั้นในการจัดสรรบล็อกล่วงหน้าคุณต้องโทรแยกต่างหากreserve()และไม่จำเป็นต้องอยู่ในสถานที่ก่อสร้างเดียวกัน (แน่นอนว่าสามารถ / ควรจะเป็นในภายหลังหลังจากที่คุณทราบขนาดที่ต้องการเพื่อรองรับ)
  • ดังนั้นหากเวกเตอร์จะจัดสรรบล็อกหน่วยความจำของขนาดการใช้งานที่กำหนดไว้ล่วงหน้าเสมอสิ่งนี้จะทำให้งานที่ตั้งใจไว้reserve()ไม่ดีหรือไม่?
  • อะไรคือข้อดีของการจัดสรรบล็อกล่วงหน้าหาก STL ไม่สามารถทราบจุดประสงค์และขนาดที่คาดหวังของเวกเตอร์ได้โดยธรรมชาติ มันจะค่อนข้างไร้สาระถ้าไม่ตอบโต้
  • วิธีการแก้ปัญหาที่เหมาะสมแทนคือการจัดสรรและป้องกันการใช้งานเฉพาะกับครั้งแรกpush_back()- ถ้าไม่ได้รับการจัดสรร explicitely reserve()ก่อน
  • ในกรณีของการจัดสรรใหม่ที่จำเป็นการเพิ่มขนาดบล็อกก็เป็นการใช้งานที่เฉพาะเจาะจงเช่นกัน การใช้งานเวกเตอร์ที่ฉันรู้จักเริ่มต้นด้วยการเพิ่มขนาดแบบเอ็กซ์โพเนนเชียล แต่จะ จำกัด อัตราการเพิ่มที่ค่าสูงสุดเพื่อหลีกเลี่ยงการสูญเสียหน่วยความจำจำนวนมากหรือแม้แต่เป่ามัน

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


4

จากคำตอบอื่น ๆ เพิ่มเติมเล็กน้อยฉันพบว่าเมื่อทำงานภายใต้เงื่อนไขการดีบักด้วย Visual Studio เวกเตอร์ที่สร้างตามค่าเริ่มต้นจะยังคงจัดสรรบนฮีปแม้ว่าความจุจะเริ่มต้นที่ศูนย์ก็ตาม

โดยเฉพาะถ้า _ITERATOR_DEBUG_LEVEL! = 0 แล้วเวกเตอร์จะจัดสรรพื้นที่เพื่อช่วยในการตรวจสอบตัววนซ้ำ

https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level

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


ที่น่าสนใจพวกเขาทำลายการรับประกันโดยไม่มีข้อยกเว้น (อย่างน้อยสำหรับ C + 17 ก่อนหน้านี้?): en.cppreference.com/w/cpp/container/vector/vector
Deduplicator

4

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

แต่ผมจะอธิบายว่าทำไมมันไม่ได้ทำให้ความรู้สึกสำหรับการดำเนินงาน STL ใด ๆ ที่จะจัดสรรหน่วยความจำเมื่อการก่อสร้างของstd::vector<T>วัตถุ ;

  1. std::vector<T> ประเภทที่ไม่สมบูรณ์

    ก่อนหน้า C ++ 17 เป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในการสร้างstd::vector<T>หากTยังไม่ทราบความหมายของคำจำกัดความที่จุดของการสร้างอินสแตนซ์ อย่างไรก็ตามข้อ จำกัด ที่ได้รับการผ่อนคลายใน C

    ในการจัดสรรหน่วยความจำสำหรับวัตถุอย่างมีประสิทธิภาพคุณจำเป็นต้องทราบขนาดของมัน ตั้งแต่ C ++ 17 ขึ้นไปลูกค้าของคุณอาจมีกรณีที่std::vector<T>ชั้นเรียนของคุณไม่ทราบขนาดของT. การมีลักษณะการจัดสรรหน่วยความจำขึ้นอยู่กับประเภทความสมบูรณ์หรือไม่?

  2. Unwanted Memory allocations

    มีหลายต่อหลายครั้งที่คุณต้องสร้างแบบจำลองกราฟในซอฟต์แวร์ (ต้นไม้คือกราฟ); คุณมักจะสร้างแบบจำลองเช่น:

    class Node {
        ....
        std::vector<Node> children; //or std::vector< *some pointer type* > children;
        ....
     };
    

    ตอนนี้คิดสักครู่และจินตนาการว่าคุณมีโหนดเทอร์มินัลจำนวนมาก คุณจะโกรธมากหากการใช้งาน STL ของคุณจัดสรรหน่วยความจำเพิ่มเติมเพียงแค่คาดว่าจะมีวัตถุเข้าchildrenมา

    นี่เป็นเพียงตัวอย่างเดียวอย่าลังเลที่จะคิดเพิ่มเติม ...


2

Standard ไม่ได้ระบุค่าเริ่มต้นสำหรับความจุ แต่คอนเทนเนอร์ STL จะเติบโตโดยอัตโนมัติเพื่อรองรับข้อมูลได้มากเท่าที่คุณใส่หากคุณไม่เกินขนาดสูงสุด (ใช้ฟังก์ชัน max_size member เพื่อทราบ) สำหรับเวกเตอร์และสตริงการเติบโตจะถูกจัดการโดย realloc เมื่อใดก็ตามที่ต้องการพื้นที่เพิ่มขึ้น สมมติว่าคุณต้องการสร้างเวกเตอร์ที่ถือค่า 1-1000 โดยทั่วไปแล้วรหัสจะส่งผลให้ไม่มีการจัดสรรซ้ำระหว่าง 2 ถึง 18 ครั้งในระหว่างลูปต่อไปนี้:

vector<int> v;
for ( int i = 1; i <= 1000; i++) v.push_back(i);

การแก้ไขรหัสเพื่อใช้การจองอาจทำให้เกิดการจัดสรร 0 ระหว่างลูป:

vector<int> v;
v.reserve(1000);

for ( int i = 1; i <= 1000; i++) v.push_back(i);

กล่าวโดยประมาณว่าความจุของเวกเตอร์และสตริงจะเพิ่มขึ้นโดยมีค่าระหว่าง 1.5 ถึง 2 ในแต่ละครั้ง

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