C ++ में वेक्टर की प्रारंभिक क्षमता


90

क्या है capacity()एक की std::vectorजो कि डिफ़ॉल्ट constuctor का उपयोग कर बनाई गई है? मुझे पता है कि size()शून्य है। क्या हम बता सकते हैं कि एक डिफ़ॉल्ट निर्मित वेक्टर ढेर मेमोरी आवंटन नहीं कहता है?

इस तरह एक एकल आवंटन का उपयोग करके एक मनमाना रिजर्व के साथ एक सरणी बनाना संभव होगा std::vector<int> iv; iv.reserve(2345);। मान लीजिए कि किसी कारण से, मैं size()2345 को शुरू नहीं करना चाहता हूं ।

उदाहरण के लिए, लिनक्स पर (g ++ 4.4.5, कर्नेल 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। क्या यह एक नियम है, या क्या यह एसटीएल विक्रेता पर निर्भर है?


7
मानक वेक्टर की प्रारंभिक क्षमता के बारे में कुछ भी निर्दिष्ट नहीं करता है लेकिन अधिकांश कार्यान्वयन 0 का उपयोग करते हैं।
Mr.Anubis

11
इसकी कोई गारंटी नहीं है, लेकिन मैं किसी भी कार्यान्वयन की गुणवत्ता पर गंभीरता से सवाल उठाऊंगा जो मेरे बिना किसी अनुरोध के स्मृति को आवंटित करता है।
माइक सीमोर

2
@ मायकेस्मोर असहमत। वास्तव में उच्च निष्पादन कार्यान्वयन में एक छोटा सा इनलाइन बफर हो सकता है, जिसमें प्रारंभिक क्षमता () को सेट करने की स्थिति में यह समझ में आता है।
एलेस्टेयर

6
@alastair swapसभी पुनरावृत्तियों और संदर्भों का उपयोग करते समय मान्य ( end()s को छोड़कर ) मान्य रहते हैं । इसका मतलब है कि इनलाइन बफर संभव नहीं है।
Notinlist

जवाबों:


73

मानक निर्दिष्ट नहीं करता है कि capacityएक कंटेनर का प्रारंभिक क्या होना चाहिए, इसलिए आप कार्यान्वयन पर भरोसा कर रहे हैं। एक सामान्य कार्यान्वयन शून्य पर क्षमता शुरू करेगा, लेकिन इसकी कोई गारंटी नहीं है। दूसरी ओर std::vector<int> iv; iv.reserve(2345);इसके साथ अपनी छड़ी की रणनीति को बेहतर बनाने का कोई तरीका नहीं है।


1
मैं आपका अंतिम कथन नहीं खरीदता। यदि आप शुरू में 0 होने की क्षमता पर भरोसा नहीं कर सकते हैं, तो आप अपने वेक्टर को प्रारंभिक आकार देने की अनुमति देने के लिए अपने कार्यक्रम का पुनर्गठन कर सकते हैं। यह ढेर-स्मृति अनुरोधों की संख्या (2 से 1 तक) की आधी होगी।
बिटमैस्क

4
@bitmask: व्यावहारिक होना: क्या आप किसी भी कार्यान्वयन के बारे में जानते हैं जहां एक वेक्टर डिफ़ॉल्ट कंस्ट्रक्टर में मेमोरी आवंटित करता है? यह मानक की गारंटी नहीं है, लेकिन जैसा कि माइक सीमोर बताते हैं कि आवश्यकता के बिना आवंटन को ट्रिगर करना कार्यान्वयन की गुणवत्ता के बारे में एक बुरी गंध होगी ।
डेविड रॉड्रिग्ज - dribeas 20

3
@ DavidRodríguez-dribeas: वह बात नहीं है। आधार यह था कि "आप अपनी वर्तमान रणनीति से बेहतर नहीं कर सकते, इसलिए यह सोचकर परेशान न हों कि क्या मूर्खतापूर्ण कार्यान्वयन हो सकता है"। यदि आधार "ऐसा कोई कार्यान्वयन नहीं है, तो परेशान न हों" मैं इसे खरीदूंगा। निष्कर्ष सत्य होता है, लेकिन निहितार्थ काम नहीं करता है। क्षमा करें, शायद मैं नाइटिंग चुन रहा हूं।
bitmask

3
@bitmask यदि कोई कार्यान्वयन मौजूद है जो डिफ़ॉल्ट निर्माण पर मेमोरी आवंटित करता है, तो आपने जो कहा था, वह आवंटन की संख्या को आधा कर देगा। लेकिन vector::reserveएक प्रारंभिक आकार निर्दिष्ट करने के समान नहीं है। वेक्टर निर्माता जो प्रारंभिक आकार मान लेते हैं / nऑब्जेक्ट्स को कॉपी करते हैं, और इस प्रकार रैखिक जटिलता होती है। OTOH, कॉलिंग रिज़र्व का अर्थ केवल size()तत्वों की प्रतिलिपि बनाना / हिलाना है यदि एक वास्तविक स्थिति को ट्रिगर किया जाता है। एक खाली वेक्टर पर कॉपी करने के लिए कुछ भी नहीं है। तो बाद में भी वांछनीय हो सकता है, भले ही कार्यान्वयन डिफ़ॉल्ट रूप से निर्मित वेक्टर के लिए मेमोरी आवंटित करता है।
प्रेटोरियन

4
@bitmask, यदि आप इस डिग्री के आवंटन के बारे में चिंतित हैं तो आपको अपने विशेष मानक पुस्तकालय के कार्यान्वयन को देखना चाहिए और अटकलों पर भरोसा नहीं करना चाहिए।
मार्क रैनसम

36

एसटीडी का भंडारण कार्यान्वयन :: वेक्टर में काफी भिन्नता है, लेकिन सभी वे हैं जो मैं 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

वैसे आप लगभग हर जगह पाते हैं कि गति उद्देश्यों के लिए सिफारिश लगभग हमेशा एक वेक्टर का उपयोग करने के लिए होती है, इसलिए यदि आप कुछ भी कर रहे हैं जिसमें स्पार्स डेटा शामिल है ...
एंड्रयू

@ और उन्हें क्या शुरू करना चाहिए था? यदि कुछ प्रोग्रामर डिफ़ॉल्ट से अधिक आरक्षित करना चाहता है तो कुछ भी आवंटित करना केवल उस समय को आवंटित करना और उस मेमोरी को नष्ट करना होगा। यदि आप मान रहे हैं कि उन्हें 1 से शुरू करना चाहिए, तो यह आवंटित करेगा कि जैसे ही कोई 1 वैसे भी आवंटित कर रहा है।
पोद्दले

@ मूल्य आप इसे अंकित मूल्य पर लेने के बजाय लाइनों के बीच में पढ़ रहे हैं। सुराग कि यह व्यंग्य नहीं है "स्मार्ट" शब्द है, साथ ही साथ मेरी दूसरी टिप्पणी में विरल डेटा का उल्लेख है।
एंड्रयू

@ और ओह अच्छा, आप काफी राहत महसूस कर रहे थे, उन्होंने इसे 0. शुरू कर दिया। क्यों मजाक में भी इसके बारे में टिप्पणी की?
पुड्डल

7

जहां तक ​​मुझे मानक समझ में आया (हालांकि मैं वास्तव में एक संदर्भ का नाम नहीं दे सकता था), कंटेनर इंस्टेंशन और मेमोरी आवंटन को जानबूझकर अच्छे कारण के लिए डिकूप्ट किया गया है। इसके लिए आपके पास अलग, अलग कॉल है

  • constructor खुद कंटेनर बनाने के लिए
  • reserve() कम से कम (!) वस्तुओं की एक दी गई संख्या को समायोजित करने के लिए एक उपयुक्त बड़ी मेमोरी ब्लॉक आवंटित करना

और यह बहुत मायने रखता है। इसके लिए अस्तित्व का एकमात्र अधिकार reserve()आपको वेक्टर को बढ़ने पर संभवतः महंगी वसूली के आसपास कोड करने का अवसर देना है। उपयोगी होने के लिए आपको वस्तुओं की संख्या को जानना होगा या कम से कम एक शिक्षित अनुमान लगाने में सक्षम होने की आवश्यकता होगी। यदि यह आपको दिया नहीं जाता है तो आप इससे दूर रहें reserve()क्योंकि आप व्यर्थ की याददाश्त के लिए वास्तविक परिवर्तन को बदल देंगे।

तो यह सब एक साथ डाल:

  • मानक जानबूझकर एक निर्माता को निर्दिष्ट नहीं करता है जो आपको एक विशिष्ट संख्या के लिए मेमोरी ब्लॉक आवंटित करने की अनुमति देता है (जो कि कम से कम एक वांछनीय कार्यान्वयन से अधिक वांछनीय होगा, हुड के तहत "कुछ" तय किया गया)।
  • आवंटन में निहित नहीं होना चाहिए। तो, एक ब्लॉक को प्रचारित करने के लिए आपको एक अलग कॉल करने की आवश्यकता हैreserve() आवश्यकता है और यह निर्माण के एक ही स्थान पर नहीं होना चाहिए (यदि आपको बाद में रहने के लिए आवश्यक आकार के बारे में पता चल गया हो, तो निश्चित रूप से बाद में होना चाहिए)
  • इस प्रकार यदि कोई वेक्टर हमेशा क्रियान्वित परिभाषित आकार के एक मेमोरी ब्लॉक का प्रचार करेगा, तो इसका उद्देश्य reserve()क्या होगा, नहीं?
  • यदि ब्लॉक एसटीएल स्वाभाविक रूप से एक वेक्टर के अपेक्षित उद्देश्य और अपेक्षित आकार को नहीं जान सकता है, तो एक ब्लॉक के प्रचार से क्या फायदा होगा? यह बल्कि निरर्थक होगा, यदि प्रति-उत्पादक नहीं है।
  • इसके बजाय उचित समाधान पहले के साथ विशिष्ट ब्लॉक को आवंटित और कार्यान्वित करना है push_back()- यदि पहले से ही पहले से ही स्पष्ट रूप से आवंटित नहीं किया गया है reserve()
  • एक आवश्यक वसूली के मामले में ब्लॉक आकार में वृद्धि के रूप में अच्छी तरह से कार्यान्वयन है। वेक्टर कार्यान्वयन मैं आकार में एक घातीय वृद्धि के साथ शुरू करने के बारे में जानता हूं, लेकिन बड़ी मात्रा में स्मृति को बर्बाद करने या यहां तक ​​कि इसे उड़ाने से बचने के लिए एक निश्चित अधिकतम पर वेतन वृद्धि दर को कैप करेगा।

यह सब पूर्ण संचालन और लाभ के लिए आता है केवल अगर एक आवंटित निर्माणकर्ता द्वारा परेशान नहीं किया जाता है। आपके पास सामान्य परिदृश्यों के लिए उचित डिफॉल्ट हैं जिन्हें reserve()(और shrink_to_fit()) द्वारा मांग पर ओवरराइड किया जा सकता है । इसलिए, भले ही मानक स्पष्ट रूप से राज्य नहीं करता है, मैं यह सुनिश्चित कर रहा हूं कि एक नवनिर्मित वेक्टर उपदेश नहीं देता है सभी मौजूदा कार्यान्वयन के लिए एक बहुत ही सुरक्षित शर्त है।


4

अन्य उत्तरों के लिए एक मामूली जोड़ के रूप में, मैंने पाया कि जब विजुअल स्टूडियो के साथ डिबग की शर्तों के तहत चल रहा है तो एक डिफ़ॉल्ट रूप से निर्मित वेक्टर अभी भी ढेर पर आवंटित करेगा, भले ही क्षमता शून्य से शुरू हो।

विशेष रूप से अगर _ITERATOR_DEBUG_LEVEL! = 0 तो वेक्टर पुनरावृत्ति जाँच में मदद करने के लिए कुछ स्थान आवंटित करेगा।

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

मुझे यह थोड़ा कष्टप्रद लगा क्योंकि मैं उस समय एक कस्टम एलोकेटर का उपयोग कर रहा था और अतिरिक्त आवंटन की उम्मीद नहीं कर रहा था।


दिलचस्प, वे टूट noexcept-गारंटी देता है (सी + 17, पहले के लिए कम से कम?): En.cppreference.com/w/cpp/container/vector/vector
Deduplicator

4

यह एक पुराना प्रश्न है, और यहां सभी उत्तरों ने मानक दृष्टिकोण को सही तरीके से समझाया है और जिस तरह से आप एक पोर्टेबल तरीके से प्रारंभिक क्षमता प्राप्त कर सकते हैं std::vector::reserve;

हालाँकि, मैं समझाता हूँ कि किसी एसटीएल कार्यान्वयन के लिए किसी std::vector<T>वस्तु के निर्माण पर मेमोरी आवंटित करने का कोई मतलब क्यों नहीं है ;

  1. std::vector<T> अपूर्ण प्रकार के;

    C ++ 17 से पहले, यह अनिर्धारित व्यवहार करने के लिए किया गया था std::vector<T>कि यदि निर्माण की परिभाषा Tअभी भी तात्कालिकता के बिंदु पर अज्ञात है। हालांकि, उस बाधा को C ++ 17 में आराम दिया गया था

    किसी ऑब्जेक्ट के लिए स्मृति को कुशलतापूर्वक आवंटित करने के लिए, आपको उसका आकार जानना होगा। C ++ 17 और उसके बाद से, आपके ग्राहकों के पास ऐसे मामले हो सकते हैं जहां आपकी std::vector<T>कक्षा के आकार का पता नहीं है T। क्या यह स्मृति आवंटन विशेषताओं को पूर्णता पर निर्भर करने के लिए समझ में आता है?

  2. Unwanted Memory allocations

    कई, कई, कई बार आपको सॉफ्टवेयर में एक ग्राफ की आवश्यकता होगी। (एक पेड़ एक ग्राफ है); आप इसे पसंद करने की संभावना रखते हैं:

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

    अब एक पल के लिए सोचें और कल्पना करें कि क्या आपके पास बहुत सारे टर्मिनल नोड्स थे। यदि आपका एसटीएल कार्यान्वयन वस्तुओं में होने की प्रत्याशा में अतिरिक्त मेमोरी आवंटित करता है तो आपको बहुत अफ़सोस होगा children

    यह सिर्फ एक उदाहरण है, अधिक सोचने के लिए स्वतंत्र महसूस करें ...


2

मानक doesn't क्षमता के लिए प्रारंभिक मान निर्दिष्ट करता है, लेकिन STL कंटेनर स्वचालित रूप से आपके द्वारा डाले गए डेटा को अधिक से अधिक बढ़ने देता है, बशर्ते कि आप अधिकतम आकार से अधिक न हों (अधिकतम पता करने के लिए सदस्य फ़ंक्शन का उपयोग करें)। वेक्टर और स्ट्रिंग के लिए, विकास को वास्तविक स्थान द्वारा नियंत्रित किया जाता है जब भी अधिक स्थान की आवश्यकता होती है। मान लीजिए आप एक वेक्टर होल्डिंग वेल्यू 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 के बीच के कारक से बढ़ती हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.