c++ मुझे ऑब्जेक्ट के बजाए पॉइंटर का उपयोग क्यों करना चाहिए?




pointers c++11 (18)

यह बहुत दुर्भाग्यपूर्ण है कि आप अक्सर गतिशील आवंटन देखते हैं। यह दिखाता है कि कितने खराब सी ++ प्रोग्रामर हैं।

एक मायने में, आपके पास दो प्रश्न हैं जो एक में बंडल किए गए हैं। सबसे पहले हमें गतिशील आवंटन ( new का उपयोग करके) कब उपयोग करना चाहिए? दूसरी बात यह है कि हम पॉइंटर्स का उपयोग कब करते हैं?

महत्वपूर्ण ले-होम संदेश यह है कि आपको हमेशा नौकरी के लिए उचित उपकरण का उपयोग करना चाहिए। लगभग सभी परिस्थितियों में, मैन्युअल गतिशील आवंटन और / या कच्चे पॉइंटर्स का उपयोग करने से कुछ और उचित और सुरक्षित है।

गतिशील आवंटन

आपके प्रश्न में, आपने ऑब्जेक्ट बनाने के दो तरीके प्रदर्शित किए हैं। मुख्य अंतर वस्तु की भंडारण अवधि है। Object myObject; एक ब्लॉक के भीतर, ऑब्जेक्ट स्वचालित संग्रहण अवधि के साथ बनाया जाता है, जिसका अर्थ है कि यह दायरे से बाहर होने पर स्वचालित रूप से नष्ट हो जाएगा। जब आप new Object() , तो ऑब्जेक्ट में गतिशील संग्रहण अवधि होती है, जिसका अर्थ है कि यह तब तक जीवित रहता है जब तक आप इसे स्पष्ट रूप से delete नहीं देते। जब आपको इसकी आवश्यकता हो तो आपको केवल गतिशील संग्रहण अवधि का उपयोग करना चाहिए। यही है, आप हमेशा जब आप कर सकते हैं स्वचालित भंडारण अवधि के साथ वस्तुओं को बनाना पसंद करते हैं

मुख्य दो स्थितियों जिसमें आपको गतिशील आवंटन की आवश्यकता हो सकती है:

  1. आपको मौजूदा दायरे को पार करने के लिए ऑब्जेक्ट की आवश्यकता है - उस विशिष्ट स्मृति स्थान पर उस विशिष्ट वस्तु, इसकी एक प्रति नहीं। यदि आप वस्तु को प्रतिलिपि / स्थानांतरित करने के ठीक हैं (अधिकांश समय आपको होना चाहिए), तो आपको एक स्वचालित ऑब्जेक्ट पसंद करना चाहिए।
  2. आपको बहुत सारी मेमोरी आवंटित करने की आवश्यकता है , जो आसानी से ढेर को भर सकता है। यह अच्छा होगा अगर हमें इस बारे में चिंता करने की ज़रूरत नहीं थी (ज्यादातर समय आपको नहीं करना चाहिए), क्योंकि यह वास्तव में सी ++ के दायरे से बाहर है, लेकिन दुर्भाग्य से हमें सिस्टम की वास्तविकता से निपटना होगा के लिए विकास कर रहे हैं।

जब आपको पूरी तरह गतिशील आवंटन की आवश्यकता होती है, तो आपको इसे एक स्मार्ट पॉइंटर या किसी अन्य प्रकार में RAII (मानक कंटेनर की तरह) करने के लिए इसे समाहित करना चाहिए। स्मार्ट पॉइंटर्स गतिशील आवंटित वस्तुओं के स्वामित्व अर्थशास्त्र प्रदान करते हैं। उदाहरण के लिए std::unique_ptr और std::shared_ptr पर एक नज़र डालें। यदि आप उन्हें उचित रूप से उपयोग करते हैं, तो आप लगभग पूरी तरह से अपना खुद का मेमोरी प्रबंधन करने से बच सकते हैं ( शून्य का नियम देखें)।

संकेत

हालांकि, गतिशील आवंटन से परे कच्चे पॉइंटर्स के लिए अन्य सामान्य उपयोग हैं, लेकिन अधिकांश विकल्पों में आपको पसंद करना चाहिए। पहले की तरह, हमेशा विकल्पों को प्राथमिकता दें जब तक कि आपको वास्तव में पॉइंटर्स की आवश्यकता न हो

  1. आपको संदर्भ अर्थशास्त्र की आवश्यकता है । कभी-कभी आप किसी ऑब्जेक्ट को पॉइंटर का उपयोग करके पास करना चाहते हैं (भले ही इसे आवंटित किया गया हो) क्योंकि आप उस कार्य को चाहते हैं जिसे आप पास कर रहे हैं, उस विशिष्ट ऑब्जेक्ट (इसकी प्रति नहीं) तक पहुंचने के लिए। हालांकि, ज्यादातर स्थितियों में, आपको पॉइंटर्स को संदर्भ प्रकार पसंद करना चाहिए, क्योंकि यह विशेष रूप से उनके लिए डिज़ाइन किया गया है। ध्यान दें कि उपरोक्त स्थिति 1 में, जैसा कि उपरोक्त स्थिति 1 से ऊपर की वस्तु के जीवनकाल को विस्तारित करने के बारे में जरूरी नहीं है। पहले की तरह, यदि आप ऑब्जेक्ट की प्रतिलिपि पास करने के ठीक हैं, तो आपको संदर्भ अर्थशास्त्र की आवश्यकता नहीं है।

  2. आपको बहुरूपता की आवश्यकता है । आप ऑब्जेक्ट के पॉइंटर या संदर्भ के माध्यम से केवल पॉलीमोर्फिक रूप से फ़ंक्शन को कॉल कर सकते हैं (यानी, किसी ऑब्जेक्ट के गतिशील प्रकार के अनुसार)। यदि यह व्यवहार आपको चाहिए, तो आपको पॉइंटर्स या संदर्भों का उपयोग करने की आवश्यकता है। फिर, संदर्भों को प्राथमिकता दी जानी चाहिए।

  3. आप यह प्रतिनिधित्व करना चाहते हैं कि ऑब्जेक्ट छोड़ा जा रहा है जब एक nullptr पारित करने की अनुमति देकर एक वस्तु वैकल्पिक है। यदि यह एक तर्क है, तो आपको डिफ़ॉल्ट तर्क या फ़ंक्शन ओवरलोड का उपयोग करना पसंद करना चाहिए। अन्यथा, आपको इस प्रकार का उपयोग करना पसंद करना चाहिए जो इस व्यवहार को समाहित करता है, जैसे कि std::optional (सी ++ 17 में पेश किया गया - पहले सी ++ मानकों के साथ, boost::optional उपयोग करें)।

  4. आप संकलन समय को बेहतर बनाने के लिए संकलन इकाइयों को रद्द करना चाहते हैं । पॉइंटर की उपयोगी संपत्ति यह है कि आपको केवल इशारा करने के लिए (ऑब्जेक्ट का उपयोग करने के लिए, आपको एक परिभाषा की आवश्यकता होगी) की अग्रेषित घोषणा की आवश्यकता होती है। यह आपको अपनी संकलन प्रक्रिया के कुछ हिस्सों को कम करने की अनुमति देता है, जो संकलन समय में काफी सुधार कर सकता है। Pimpl मुहावरे देखें।

  5. आपको सी लाइब्रेरी या सी-स्टाइल लाइब्रेरी के साथ इंटरफेस करने की आवश्यकता है । इस बिंदु पर, आपको कच्चे पॉइंटर्स का उपयोग करने के लिए मजबूर होना पड़ता है। सबसे अच्छी बात यह है कि आप यह सुनिश्चित कर सकते हैं कि आप अपने कच्चे पॉइंटर्स को आखिरी संभव पल में ही छोड़ दें। आप एक स्मार्ट पॉइंटर से कच्चे सूचक प्राप्त कर सकते हैं, उदाहरण के लिए, इसके सदस्य सदस्य फ़ंक्शन का उपयोग करके। यदि कोई लाइब्रेरी आपके लिए कुछ आवंटन करती है, जो आपको उम्मीद है कि आप एक हैंडल के माध्यम से डिलीकेट करना चाहते हैं, तो आप अक्सर कस्टम पॉइंटर में हैंडल को कस्टम डिलीटर के साथ लपेट सकते हैं जो ऑब्जेक्ट को उचित रूप से डिलीकेट करेगा।

मैं जावा पृष्ठभूमि से आ रहा हूं और सी ++ में ऑब्जेक्ट्स के साथ काम करना शुरू कर दिया है। लेकिन मेरे साथ एक चीज यह है कि लोग अक्सर ऑब्जेक्ट्स के बजाय ऑब्जेक्ट्स के पॉइंटर्स का उपयोग करते हैं, उदाहरण के लिए यह घोषणा:

Object *myObject = new Object;

बजाय:

Object myObject;

या फ़ंक्शन का उपयोग करने के बजाय, आइए testFunc() , इस तरह कहें:

myObject.testFunc();

हमें लिखना है:

myObject->testFunc();

लेकिन मैं यह नहीं समझ सकता कि हमें ऐसा क्यों करना चाहिए। मुझे लगता है कि इसे दक्षता और गति से करना है क्योंकि हमें स्मृति पते तक सीधे पहुंच प्राप्त होती है। क्या मैं सही हू?


There are many benefits of using pointers to object -

  1. Efficiency (as you already pointed out). Passing objects to functions mean creating new copies of object.
  2. Working with objects from third party libraries. If your object belongs to a third party code and the authors intend the usage of their objects through pointers only (no copy constructors etc) the only way you can pass around this object is using pointers. Passing by value may cause issues. (Deep copy / shallow copy issues).
  3. if the object owns a resource and you want that the ownership should not be sahred with other objects.

There are many excellent answers already, but let me give you one example:

I have an simple Item class:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

I make a vector to hold a bunch of them.

std::vector<Item> inventory;

I create one million Item objects, and push them back onto the vector. I sort the vector by name, and then do a simple iterative binary search for a particular item name. I test the program, and it takes over 8 minutes to finish executing. Then I change my inventory vector like so:

std::vector<Item *> inventory;

...and create my million Item objects via new. The ONLY changes I make to my code are to use the pointers to Items, excepting a loop I add for memory cleanup at the end. That program runs in under 40 seconds, or better than a 10x speed increase. EDIT: The code is at http://pastebin.com/DK24SPeW With compiler optimizations it shows only a 3.4x increase on the machine I just tested it on, which is still considerable.


लेकिन मैं यह नहीं समझ सकता कि हमें इसका इस्तेमाल क्यों करना चाहिए?

यदि आप इसका उपयोग करते हैं तो मैं फ़ंक्शन बॉडी के अंदर काम करता हूं इसकी तुलना करूंगा:

Object myObject;

फ़ंक्शन के अंदर, यह फ़ंक्शन लौटने के बाद आपका myObject नष्ट हो जाएगा। तो यह उपयोगी है अगर आपको अपने कार्य के बाहर अपनी वस्तु की आवश्यकता नहीं है। यह ऑब्जेक्ट वर्तमान थ्रेड स्टैक पर रखा जाएगा।

यदि आप फ़ंक्शन बॉडी के अंदर लिखते हैं:

 Object *myObject = new Object;

फिर फंक्शन समाप्त होने के बाद myObject द्वारा इंगित ऑब्जेक्ट क्लास इंस्टेंस नष्ट नहीं होगा, और आवंटन ढेर पर है।

अब यदि आप जावा प्रोग्रामर हैं, तो दूसरा उदाहरण जावा के तहत ऑब्जेक्ट आवंटन कैसे काम करता है इसके करीब है। यह पंक्ति: Object *myObject = new Object; जावा के बराबर है: Object myObject = new Object(); । अंतर यह है कि जावा myObject के तहत कचरा इकट्ठा किया जाएगा, जबकि सी ++ के तहत इसे मुक्त नहीं किया जाएगा, आपको कहीं भी स्पष्ट रूप से 'myObject को हटाएं' कहा जाना चाहिए; अन्यथा आप मेमोरी लीक पेश करेंगे।

चूंकि सी ++ 11 आप गतिशील आवंटन के सुरक्षित तरीकों का उपयोग कर सकते हैं: new Object , shared_ptr / unique_ptr में मान संग्रहीत करके।

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

भी, वस्तुओं को अक्सर कंटेनर में संग्रहीत किया जाता है, जैसे नक्शा या वेक्टर-एस, वे स्वचालित रूप से आपके ऑब्जेक्ट्स का जीवनकाल प्रबंधित करेंगे।


Well the main question is Why should I use a pointer rather than the object itself? And my answer, you should (almost) never use pointer instead of object, because C++ has references , it is safer then pointers and guarantees the same performance as pointers.

Another thing you mentioned in your question:

Object *myObject = new Object;

यह कैसे काम करता है? It creates pointer of Object type, allocates memory to fit one object and calls default constructor, sounds good, right? But actually it isn't so good, if you dynamically allocated memory (used keyword new ), you also have to free memory manually, that means in code you should have:

delete myObject;

This calls destructor and frees memory, looks easy, however in big projects may be difficult to detect if one thread freed memory or not, but for that purpose you can try shared pointers , these slightly decreases performance, but it is much easier to work with them.

And now some introduction is over and go back to question.

You can use pointers instead of objects to get better performance while transferring data between function.

Take a look, you have std::string (it is also object) and it contains really much data, for example big XML, now you need to parse it, but for that you have function void foo(...) which can be declarated in different ways:

  1. void foo(std::string xml); In this case you will copy all data from your variable to function stack, it takes some time, so your performance will be low.
  2. void foo(std::string* xml); In this case you will pass pointer to object, same speed as passing size_t variable, however this declaration has error prone, because you can pass NULL pointer or invalid pointer. Pointers usually used in C because it doesn't have references.
  3. void foo(std::string& xml); Here you pass reference, basically it is the same as passing pointer, but compiler does some stuff and you cannot pass invalid reference (actually it is possible to create situation with invalid reference, but it is tricking compiler).
  4. void foo(const std::string* xml); Here is the same as second, just pointer value cannot be changed.
  5. void foo(const std::string& xml); Here is the same as third, but object value cannot be changed.

What more I want to mention, you can use these 5 ways to pass data no matter which allocation way you have chosen (with new or regular ).

Another thing to mention, when you create object in regular way, you allocate memory in stack, but while you create it with new you allocate heap. It is much faster to allocate stack, but it is kind a small for really big arrays of data, so if you need big object you should use heap, because you may get , but usually this issue is solved using STL containers and remember std::string is also container, some guys forgot it :)


तकनीकी रूप से यह एक स्मृति आवंटन मुद्दा है, हालांकि यहां इसके दो और व्यावहारिक पहलू हैं। इसे दो चीजों के साथ करना है: 1) स्कोप, जब आप किसी ऑब्जेक्ट को पॉइंटर के बिना परिभाषित करते हैं तो आप उस कोड ब्लॉक के बाद इसे एक्सेस नहीं कर पाएंगे, जबकि यदि आप "नया" के साथ पॉइंटर को परिभाषित करते हैं तो आप can access it from anywhere you have a pointer to this memory until you call "delete" on the same pointer. 2) If you want to pass arguments to a function you want to pass a pointer or a reference in order to be more efficient. When you pass an Object then the object is copied, if this is an object that uses a lot of memory this might be CPU consuming (eg you copy a vector full of data). When you pass a pointer all you pass is one int (depending of implementation but most of them are one int).

Other than that you need to understand that "new" allocates memory on the heap that needs to be freed at some point. When you don't have to use "new" I suggest you use a regular object definition "on the stack".


सी ++ आपको किसी ऑब्जेक्ट को पास करने के तीन तरीके देता है: पॉइंटर द्वारा, संदर्भ द्वारा, और मूल्य के अनुसार। जावा आपको बाद वाले के साथ सीमित करता है (केवल अपवाद प्राचीन प्रकार जैसे int, बूलियन आदि) है। यदि आप एक अजीब खिलौने की तरह सी ++ का उपयोग नहीं करना चाहते हैं, तो आप इन तीन तरीकों के बीच अंतर को बेहतर ढंग से जानना चाहेंगे।

जावा का दावा है कि ऐसी कोई समस्या नहीं है कि 'कौन और कब इसे नष्ट करना चाहिए?'। जवाब है: कचरा कलेक्टर, महान और भयानक। फिर भी, यह स्मृति रिसाव के खिलाफ 100% सुरक्षा प्रदान नहीं कर सकता (हाँ, जावा स्मृति रिसाव कर सकते हैं )। असल में, जीसी आपको सुरक्षा की झूठी भावना देता है। आपका एसयूवी जितना बड़ा होगा, उतना ही लंबा होगा।

सी ++ आपको ऑब्जेक्ट के जीवन चक्र प्रबंधन के साथ आमने-सामने छोड़ देता है। खैर, इसका सामना करने के साधन हैं ( स्मार्ट प्वाइंटर्स परिवार, क्यूटी में क्यूओब्जेक्ट और इसी तरह), लेकिन उनमें से कोई भी जीसी जैसे 'आग और भूल' तरीके से उपयोग नहीं किया जा सकता है: आपको हमेशा मेमोरी हैंडलिंग को ध्यान में रखना चाहिए। न केवल किसी ऑब्जेक्ट को नष्ट करने के बारे में आपको ध्यान रखना चाहिए, आपको एक ही ऑब्जेक्ट को एक से अधिक बार नष्ट करने से बचाना होगा।

अभी तक डर नहीं है? ठीक है: चक्रीय संदर्भ - उन्हें स्वयं को संभालें, मानव। और याद रखें: प्रत्येक ऑब्जेक्ट को एक बार ठीक से मार दें, हम सी ++ रनटाइम उन लोगों को पसंद नहीं करते हैं जो लाशों के साथ गड़बड़ करते हैं, अकेले मृतकों को छोड़ देते हैं।

तो, अपने प्रश्न पर वापस।

जब आप अपनी ऑब्जेक्ट को मूल्य के आधार पर पास करते हैं, पॉइंटर या संदर्भ द्वारा नहीं, तो आप ऑब्जेक्ट (संपूर्ण ऑब्जेक्ट, चाहे वह दो बाइट्स या एक विशाल डेटाबेस डंप है) - आप बाद में बचने के लिए सावधानी बरतने के लिए पर्याप्त स्मार्ट हैं, टी आप?) हर बार जब आप '=' करते हैं। और ऑब्जेक्ट के सदस्यों तक पहुंचने के लिए, आप '।' का उपयोग करते हैं। (डॉट)।

जब आप पॉइंटर द्वारा अपना ऑब्जेक्ट पास करते हैं, तो आप केवल कुछ बाइट्स (32-बिट सिस्टम पर 4, 64-बिट वाले 8 पर) कॉपी करते हैं, अर्थात् - इस ऑब्जेक्ट का पता। और इसे सभी को दिखाने के लिए, जब आप सदस्यों तक पहुंचते हैं तो आप इस फैंसी '->' ऑपरेटर का उपयोग करते हैं। या आप '*' और '।' के संयोजन का उपयोग कर सकते हैं।

जब आप संदर्भों का उपयोग करते हैं, तो आपको सूचक मिलता है जो एक मान होने का नाटक करता है। यह एक सूचक है, लेकिन आप सदस्यों को '।' के माध्यम से एक्सेस करते हैं।

और, अपने दिमाग को एक और बार उड़ाने के लिए: जब आप कॉमा द्वारा अलग किए गए कई चर घोषित करते हैं, तो (हाथ देखें):

  • हर किसी को टाइप दिया जाता है
  • मूल्य / सूचक / संदर्भ संशोधक व्यक्तिगत है

उदाहरण:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

You shouldn't . People (many people, sadly) write it out of ignorance.

Sometimes dynamic allocation has its place but, in the examples you give, it is wrong .

If you want to think about efficiency, then this is worse , because it introduces indirection for no good reason. This sort of programming is slower and more error-prone .


I will include one important use case of pointer. When you are storing some object in the base class, but it could be polymorphic.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

So in this case you can't declare bObj as an direct object, you have to have pointer.


"Necessity is the mother of invention." The most of important difference that I would like to point out is the outcome of my own experience of coding. Sometimes you need to pass objects to functions . In that case if your object is of a very big class then passing it as an object will copy its state (which you might not want ..AND CAN BE BIG OVERHEAD) thus resulting in overhead of copying object .while pointer is fixed 4 byte size (assuming 32 bit).Other reasons are already mentioned above...


A pointer directly references the memory location of an object. Java has nothing like this. Java has references that reference the location of object through hash tables. You cannot do anything like pointer arithmetic in Java with these references.

To answer your question, it's just your preference. I prefer using the Java-like syntax.


सी ++ में, स्टैक पर आवंटित ऑब्जेक्ट्स ( Object object; का उपयोग करके Object object; ब्लॉक के भीतर कथन) केवल उस दायरे में ही रहेंगे जो उन्हें घोषित किया जाता है। जब कोड का ब्लॉक निष्पादन समाप्त करता है, तो घोषित ऑब्जेक्ट नष्ट हो जाता है। जबकि यदि आप Object* obj = new Object() का उपयोग करते हुए ढेर पर स्मृति आवंटित करते हैं, तब तक वे ढेर में रहते रहेंगे जब तक आप delete obj

जब मैं ऑब्जेक्ट का उपयोग न केवल उस कोड के ब्लॉक में करता हूं जो इसे घोषित / आवंटित करता है तो मैं ढेर पर एक वस्तु बनाउंगा।


Object *myObject = new Object;

Doing this will create a reference to an Object (on the heap) which has to be deleted explicitly to avoid memory leak .

Object myObject;

Doing this will create an object(myObject) of the automatic type (on the stack) that will be automatically deleted when the object(myObject) goes out of scope.


प्रस्तावना

जावा सी ++ की तरह कुछ भी नहीं है, प्रचार के विपरीत। जावा हाइप मशीन आपको विश्वास करना चाहती है कि जावा में सी ++ वाक्यविन्यास की तरह है, कि भाषाएं समान हैं। सच्चाई से कुछ भी दूर नहीं हो सकता। यह गलतफहमी कारण है कि जावा प्रोग्रामर सी ++ पर जाते हैं और जावा कोड जैसे सिंटैक्स का उपयोग उनके कोड के प्रभाव को समझे बिना करते हैं।

आगे हम जाते हैं

लेकिन मैं यह नहीं समझ सकता कि हमें ऐसा क्यों करना चाहिए। मुझे लगता है कि इसे दक्षता और गति से करना है क्योंकि हमें स्मृति पते तक सीधे पहुंच प्राप्त होती है। क्या मैं सही हू?

इसके विपरीत, वास्तव में। ढेर ढेर की तुलना में बहुत धीमी है, क्योंकि ढेर की तुलना में ढेर बहुत सरल है। स्वचालित भंडारण चर (उर्फ स्टैक वैरिएबल) के पास उनके विनाशकों को गुंजाइश से बाहर जाने के बाद बुलाया जाता है। उदाहरण के लिए:

{
    std::string s;
}
// s is destroyed here

दूसरी ओर, यदि आप गतिशील रूप से आवंटित सूचक का उपयोग करते हैं, तो इसके विनाशक को मैन्युअल रूप से कॉल किया जाना चाहिए। आपके लिए इस विनाशक को delete दें।

{
    std::string* s = new std::string;
}
delete s; // destructor called

इसका सी # और जावा में प्रचलित new वाक्यविन्यास से कोई लेना देना नहीं है। वे पूरी तरह से अलग प्रयोजनों के लिए उपयोग किया जाता है।

गतिशील आवंटन के लाभ

1. आपको अग्रिम में सरणी के आकार को जानने की ज़रूरत नहीं है

कई सी ++ प्रोग्रामर चलने वाली पहली समस्याओं में से एक यह है कि जब वे उपयोगकर्ताओं से मनमाने ढंग से इनपुट स्वीकार कर रहे हैं, तो आप केवल एक स्टैक वैरिएबल के लिए एक निश्चित आकार आवंटित कर सकते हैं। आप या तो सरणी के आकार को बदल नहीं सकते हैं। उदाहरण के लिए:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

बेशक यदि आपने इसके बजाय std::string उपयोग किया है, तो std::string आंतरिक रूप से स्वयं का आकार बदलती है ताकि कोई समस्या न हो। लेकिन अनिवार्य रूप से इस समस्या का समाधान गतिशील आवंटन है। आप उपयोगकर्ता के इनपुट के आधार पर गतिशील स्मृति आवंटित कर सकते हैं, उदाहरण के लिए:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

साइड नोट : एक गलती कई शुरुआती चर परिवर्तनीय लंबाई सरणी का उपयोग है। यह एक जीएनयू एक्सटेंशन है और क्लैंग में भी एक है क्योंकि वे जीसीसी के कई एक्सटेंशन दर्पण करते हैं। तो निम्नलिखित int arr[n] पर भरोसा नहीं किया जाना चाहिए।

चूंकि ढेर ढेर से कहीं अधिक बड़ा है, इसलिए मनमाने ढंग से उतनी मेमोरी आवंटित / पुन: आवंटित कर सकती है, जबकि ढेर की सीमा होती है।

2. Arrays पॉइंटर्स नहीं हैं

यह एक लाभ आपको कैसे पूछता है? एक बार जब आप सरणी और पॉइंटर्स के पीछे भ्रम / मिथक समझते हैं तो जवाब स्पष्ट हो जाएगा। आमतौर पर यह माना जाता है कि वे वही हैं, लेकिन वे नहीं हैं। यह मिथक इस तथ्य से आता है कि पॉइंटर्स को सरणी की तरह ही सब्सक्राइब किया जा सकता है और क्योंकि एरे फ़ंक्शन घोषणा में शीर्ष स्तर पर पॉइंटर्स को क्षय हो जाते हैं। हालांकि, एक बार एक सरणी एक सूचक के लिए क्षय हो जाता है, तो सूचक अपनी sizeof जानकारी खो देता है। तो sizeof(pointer) बाइट्स में पॉइंटर का आकार देगा, जो 64-बिट सिस्टम पर आमतौर पर 8 बाइट्स होता है।

आप सरणी को असाइन नहीं कर सकते हैं, केवल उन्हें प्रारंभ करें। उदाहरण के लिए:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

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

3. पॉलिमॉर्फिज्म

जावा और सी # में ऐसी सुविधाएं हैं जो आपको वस्तुओं को दूसरे के रूप में व्यवहार करने की अनुमति देती हैं, उदाहरण के लिए कीवर्ड के as उपयोग करना। तो अगर कोई Player ऑब्जेक्ट के रूप में Entity ऑब्जेक्ट का इलाज करना चाहता था, तो कोई Player player = Entity as Player; कर सकता था Player player = Entity as Player; यह बहुत उपयोगी है यदि आप एक सजातीय कंटेनर पर फ़ंक्शंस कॉल करना चाहते हैं जो केवल एक विशिष्ट प्रकार पर लागू होना चाहिए। कार्यक्षमता नीचे एक समान फैशन में हासिल की जा सकती है:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

तो कहें कि केवल त्रिकोणों में घुमावदार फ़ंक्शन था, यदि आप कक्षा के सभी ऑब्जेक्ट्स पर इसे कॉल करने का प्रयास करते हैं तो यह एक कंपाइलर त्रुटि होगी। dynamic_cast का उपयोग करके, आप कीवर्ड के as अनुकरण कर सकते हैं। स्पष्ट होने के लिए, यदि कोई कलाकार विफल रहता है, तो यह एक अमान्य सूचक देता है। तो !test अनिवार्य रूप से एक शॉर्टेंड है यह जांचने के लिए कि test है या अमान्य सूचक है, जिसका अर्थ है कि कास्ट विफल हो गया है।

स्वचालित चर के लाभ

सभी महान चीजों को गतिशील आवंटन करने के बाद, आप शायद सोच रहे होंगे कि हर कोई गतिशील आवंटन का उपयोग क्यों नहीं करेगा? मैंने आपको पहले ही एक कारण बताया है, ढेर धीमा है। और यदि आपको उस स्मृति की आवश्यकता नहीं है, तो आपको इसका दुरुपयोग नहीं करना चाहिए। तो यहां किसी विशेष आदेश में कुछ नुकसान हैं:

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

  • यह आवश्यक नहीं है। जावा और सी # के विपरीत जहां यह हर जगह new कीवर्ड का उपयोग करने के लिए बेवकूफ है, सी ++ में, आपको केवल इसका उपयोग करना चाहिए यदि आपको आवश्यकता हो। आम वाक्यांश जाता है, अगर आपके पास हथौड़ा है तो सब कुछ नाखून जैसा दिखता है। जबकि सी ++ से शुरू होने वाले शुरुआती पॉइंटर्स से डरते हैं और आदत के द्वारा स्टैक वेरिएबल्स का उपयोग करना सीखते हैं, जावा और सी # प्रोग्रामर इसे समझने के बिना पॉइंटर्स का उपयोग करके शुरू करते हैं! वह सचमुच गलत पैर पर उतर रहा है। आपको जो कुछ भी पता है उसे छोड़ देना चाहिए क्योंकि वाक्यविन्यास एक बात है, भाषा सीखना एक और है।

1. (एन) आरवीओ - उर्फ, (नामित) वापसी मूल्य अनुकूलन

एक ऑप्टिमाइज़ेशन कई कंपाइलर्स चीजें हैं जिन्हें एलिशन और रिटर्न वैल्यू ऑप्टिमाइज़ेशन कहा जाता है। ये चीजें अनावश्यक copys को obviate कर सकते हैं जो बहुत बड़ी वस्तुओं के लिए उपयोगी है, जैसे कई तत्व युक्त वेक्टर। आम तौर पर सामान्य अभ्यास उन्हें बड़ी वस्तुओं को कॉपी करने के बजाय स्वामित्व स्थानांतरित करने के लिए पॉइंटर्स का उपयोग करना है। इससे चलने वाले अर्थशास्त्र और स्मार्ट पॉइंटर्स की शुरुआत हुई है।

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


पॉइंटर्स का उपयोग करने का एक और अच्छा कारण आगे की घोषणाओं के लिए होगा। एक बड़ी पर्याप्त परियोजना में वे वास्तव में संकलन समय तेज कर सकते हैं।


One reason for using pointers is to interface with C functions. Another reason is to save memory; for example: instead of passing an object which contains a lot of data and has a processor-intensive copy-constructor to a function, just pass a pointer to the object, saving memory and speed especially if you're in a loop, however a reference would be better in that case, unless you're using an C-style array.


पॉइंटर्स के लिए कई उपयोग मामले हैं।

पॉलिमॉर्फिक व्यवहार । पॉलिमॉर्फिक प्रकारों के लिए, स्लाइसिंग से बचने के लिए पॉइंटर्स (या संदर्भ) का उपयोग किया जाता है:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

संदर्भ अर्थशास्त्र और प्रतिलिपि से परहेज । गैर-बहुलक प्रकारों के लिए, एक सूचक (या एक संदर्भ) एक संभावित महंगी वस्तु की प्रतिलिपि से बचने से बच जाएगा

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

ध्यान दें कि सी ++ 11 ने अर्थशास्त्र को स्थानांतरित किया है जो महंगे ऑब्जेक्ट्स की कई प्रतियों को फ़ंक्शन तर्क में और वापसी मानों के रूप में टाल सकता है। लेकिन एक सूचक का उपयोग निश्चित रूप से उनसे बच जाएगा और एक ही वस्तु पर एकाधिक पॉइंटर्स की अनुमति देगा (जबकि एक वस्तु केवल एक बार से स्थानांतरित की जा सकती है)।

संसाधन अधिग्रहण new ऑपरेटर का उपयोग कर संसाधन में पॉइंटर बनाना आधुनिक सी ++ में एक विरोधी पैटर्न है। एक विशेष संसाधन वर्ग (मानक कंटेनरों में से एक) या एक स्मार्ट सूचक ( std::unique_ptr<> या std::shared_ptr<> ) का उपयोग करें। विचार करें:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

बनाम

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

एक कच्चे सूचक को केवल "व्यू" के रूप में उपयोग किया जाना चाहिए, न कि स्वामित्व में शामिल किसी भी तरह से, सीधे निर्माण के माध्यम से या वापसी मूल्यों के माध्यम से। सी ++ एफएक्यू से यह क्यू एंड ए भी देखें।

अधिक बढ़िया जीवन-समय नियंत्रण हर बार एक साझा सूचक की प्रतिलिपि बनाई जा रही है (उदाहरण के लिए एक फ़ंक्शन तर्क के रूप में) जिस संसाधन को इंगित करता है वह जीवित रखा जा रहा है। दायरे से बाहर निकलने पर नियमित वस्तुओं ( new द्वारा या तो सीधे या आपके द्वारा संसाधन वर्ग के अंदर नहीं) नष्ट हो जाती हैं।


This is has been discussed at length, but in Java everything is a pointer. It makes no distinction between stack and heap allocations (all objects are allocated on the heap), so you don't realize you're using pointers. In C++, you can mix the two, depending on your memory requirements. Performance and memory usage is more deterministic in C++ (duh).





c++11