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


1598

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

Object *myObject = new Object;

बजाय:

Object myObject;

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

myObject.testFunc();

हमें लिखना होगा:

myObject->testFunc();

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


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

118
यदि आपको पॉइंटर्स का उपयोग करने का कोई कारण नहीं दिखता है, तो न करें। वस्तुओं को प्राथमिकता दें। रॉ पॉइंटर्स से पहले unique_ptr से पहले शेयर करें।
स्टेफान

111
नोट: जावा में, सब कुछ (मूल प्रकार को छोड़कर) एक सूचक है। इसलिए आपको इसके विपरीत पूछना चाहिए: मुझे साधारण वस्तुओं की आवश्यकता क्यों है?
कारोली होर्वाथ

117
ध्यान दें कि, जावा में, पॉइंटर्स सिंटैक्स द्वारा छिपे हुए हैं। C ++ में, एक पॉइंटर और एक नॉन-पॉइंटर के बीच का अंतर कोड में स्पष्ट किया जाता है। जावा हर जगह पॉइंटर्स का उपयोग करता है।
डैनियल मार्टिन

214
बहुत व्यापक के रूप में बंद करें ? गंभीरता से? कृपया लोग ध्यान दें कि प्रोग्रामिंग का यह जावा ++ तरीका बहुत सामान्य है और C ++ समुदाय पर सबसे महत्वपूर्ण समस्याओं में से एक है । इसके लिए गंभीरता से व्यवहार किया जाना चाहिए।
मनु ३४३ Man२६

जवाबों:


1570

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

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

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

गतिशील आवंटन

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

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

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

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

संकेत

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

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

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

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

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

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


82
"आपको वर्तमान क्षेत्र को रेखांकित करने के लिए वस्तु की आवश्यकता है।" - इस बारे में एक अतिरिक्त ध्यान दें: ऐसे मामले हैं जहां ऐसा लगता है कि आपको वर्तमान क्षेत्र को रेखांकित करने के लिए ऑब्जेक्ट की आवश्यकता है, लेकिन आप वास्तव में नहीं करते हैं। यदि आप अपनी वस्तु को वेक्टर के अंदर रखते हैं, उदाहरण के लिए, ऑब्जेक्ट को वेक्टर में कॉपी (या स्थानांतरित) किया जाएगा, और इसका दायरा समाप्त होने पर मूल वस्तु को नष्ट करने के लिए सुरक्षित है।

25
याद रखें कि s / copy / चाल / कई स्थानों पर अब। किसी वस्तु को वापस करना निश्चित रूप से एक चाल नहीं है। आपको यह भी ध्यान देना चाहिए कि एक पॉइंटर के माध्यम से किसी ऑब्जेक्ट तक पहुंचना ऑर्थोगोनल है कि यह कैसे बनाया गया था।
पिल्ला

15
मुझे इस उत्तर पर RAII का एक स्पष्ट संदर्भ याद आता है। C ++ संसाधन प्रबंधन के बारे में सब (लगभग सभी) है, और RAII इसे C ++ पर करने का तरीका है (और मुख्य समस्या जो कच्चे संकेत उत्पन्न करती है: ब्रेकिंग RAII)
Manu343726

11
स्मार्ट पॉइंटर्स C ++ 11 से पहले मौजूद थे, उदाहरण के लिए बढ़ावा देने के लिए :: shared_ptr और बढ़ावा :: scoped_ptr। अन्य परियोजनाओं के अपने समकक्ष हैं। आप स्थानांतरित शब्दार्थ नहीं प्राप्त कर सकते हैं, और std :: auto_ptr का कार्य त्रुटिपूर्ण है, इसलिए C ++ 11 चीजों में सुधार करता है, लेकिन सलाह अभी भी अच्छी है। (और एक दुखद nitpick, ऐसा नहीं पर्याप्त का उपयोग करने की है एक 11 संकलक सी ++, यह है कि सभी compilers आप संभवतः समर्थन सी ++ 11 के साथ काम करने के लिए अपने कोड चाहते हो सकता है आवश्यक है। हाँ, ओरेकल सोलारिस स्टूडियो, मैं कर रहा हूँ आप देख)।
armb

7
@ MDMoore313 आप लिख सकते हैंObject myObject(param1, etc...)
user000001

171

संकेत के लिए कई उपयोग के मामले हैं।

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

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

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

संसाधन का अधिग्रहणnewऑपरेटर का उपयोग करके संसाधन के लिए एक पॉइंटर बनाना आधुनिक C ++ में एक विरोधी पैटर्न है। एक विशेष संसाधन वर्ग (मानक कंटेनरों में से एक) या एक स्मार्ट पॉइंटर ( 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
}

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

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


17
"नए ऑपरेटर का उपयोग करते हुए संसाधन के लिए एक पॉइंटर बनाना एक एंटी-पैटर्न है" मुझे लगता है कि आप यह भी बढ़ा सकते हैं कि एक कच्चे पॉइंटर के पास खुद का एक एंटी-पैटर्न है । न केवल सृजन, बल्कि तर्क के रूप में कच्चे बिंदुओं को पारित करना या स्वामित्व हस्तांतरण को unique_ptr
प्रभावित करने वाले

1
@dyp tnx, अद्यतन और इस विषय पर C ++ FAQ Q & A का संदर्भ।
टेम्प्लेटेक्स

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

2
@JamesKanze मेरा मतलब यह नहीं था कि स्मार्ट पॉइंटर्स का इस्तेमाल हर जगह किया जाना चाहिए, सिर्फ स्वामित्व के लिए, और यह भी कि कच्चे पॉइंटर्स को स्वामित्व के लिए नहीं, बल्कि केवल विचारों के लिए इस्तेमाल किया जाना चाहिए।
टेम्प्लेटेक्स

2
@TemplateRex जो थोड़ा मूर्खतापूर्ण लगता है कि hun(b)हस्ताक्षर के ज्ञान की भी आवश्यकता होती है जब तक कि आप ठीक नहीं हैं यह जानने के बिना कि आपने संकलन तक गलत प्रकार की आपूर्ति की है। हालांकि संदर्भ मुद्दा आमतौर पर संकलन समय पर पकड़ा नहीं जाएगा और डिबग करने का अधिक प्रयास करेगा, यदि आप यह सुनिश्चित करने के लिए हस्ताक्षर की जांच कर रहे हैं कि तर्क सही हैं तो आप यह भी देख पाएंगे कि क्या कोई तर्क संदर्भ हैं इसलिए संदर्भ बिट एक गैर-समस्या का कुछ हो जाता है (विशेषकर आईडीई या पाठ संपादकों का उपयोग करते समय जो चयनित कार्यों के हस्ताक्षर दिखाते हैं)। इसके अलावा, const&
JAB

130

इस सवाल के कई उत्कृष्ट उत्तर हैं, जिसमें आगे की घोषणाओं, बहुरूपता आदि के महत्वपूर्ण उपयोग के मामलों को शामिल किया गया है, लेकिन मुझे लगता है कि आपके प्रश्न की "आत्मा" का एक हिस्सा उत्तर नहीं दिया गया है - अर्थात् जावा और सी ++ में अलग-अलग वाक्यविन्यास का क्या मतलब है।

आइए दो भाषाओं की तुलना करने वाली स्थिति की जाँच करें:

जावा:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

इसके समीपतम, है:

सी ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

आइए वैकल्पिक C ++ तरीका देखें:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

इसके बारे में सोचने का सबसे अच्छा तरीका यह है कि - अधिक या कम - जावा (अव्यवस्थित रूप से) ऑब्जेक्ट्स को पॉइंटर्स को हैंडल करता है, जबकि C ++ ऑब्जेक्ट्स या ऑब्जेक्ट्स को खुद ही पॉइंटर्स को हैंडल कर सकता है। इसके अपवाद हैं - उदाहरण के लिए, यदि आप जावा "आदिम" प्रकार की घोषणा करते हैं, तो वे वास्तविक मूल्य हैं जो कॉपी किए गए हैं, न कि पॉइंटर्स। इसलिए,

जावा:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

कहा कि, पॉइंटर्स का उपयोग करना या तो चीजों को संभालने का सही या गलत तरीका नहीं है; हालांकि अन्य जवाबों ने संतोषजनक रूप से कवर किया है। सामान्य विचार हालांकि यह है कि सी ++ में आप वस्तुओं के जीवनकाल पर अधिक नियंत्रण रखते हैं, और जहां वे रहेंगे।

होम प्वाइंट लें - Object * object = new Object()निर्माण वास्तव में वही है जो ठेठ जावा (या उस मामले के लिए सी #) के सबसे करीब है।


7
Object2 is now "dead": मुझे लगता है कि आप मतलब है myObject1या अधिक ठीक है the object pointed to by myObject1
क्लेमेंट

2
वास्तव में! थोड़ा रेफ़र किया।
गेरासिमोस आर

2
Object object1 = new Object(); Object object2 = new Object();बहुत बुरा कोड है। दूसरा नया या दूसरा ऑब्जेक्ट कंस्ट्रक्टर फेंक सकता है, और अब ऑब्जेक्ट 1 लीक हो गया है। यदि आप कच्चे newएस का उपयोग कर रहे हैं , तो आपको newRAAP रैपर ASAP में एड ऑब्जेक्ट्स को लपेटना चाहिए ।
पीएसकोलिक 15

8
वास्तव में, यह होगा यदि यह एक कार्यक्रम था, और इसके आसपास कुछ और नहीं चल रहा था। शुक्र है, यह सिर्फ एक स्पष्टीकरण स्निपेट है जिसमें दिखाया गया है कि C ++ में एक पॉइंटर कैसे व्यवहार करता है - और उन कुछ जगहों में से एक जहां RAII ऑब्जेक्ट को कच्चे पॉइंटर के लिए प्रतिस्थापित नहीं किया जा सकता है, कच्चे पॉइंटर्स के बारे में अध्ययन और सीख रहा है ...
Gimimos R

80

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


7
यह वास्तव में उपयोगी जानकारी के मिश्रण में जोड़ रहा है, इसलिए खुशी है कि आपने इसे एक उत्तर दिया है!
टेम्प्लेटेक्स

3
std :: share_ptr <T> भी T की आगे की घोषणाओं के साथ काम करता है (std :: unique_ptr <T> does )
berkus

13
@berkus: std::unique_ptr<T>आगे की घोषणाओं के साथ काम करता है T। आपको बस यह सुनिश्चित करने की आवश्यकता है कि जब विध्वंसक std::unique_ptr<T>को कहा जाता है, Tतो एक पूर्ण प्रकार है। इसका आम तौर पर मतलब है कि आपकी कक्षा std::unique_ptr<T>में हेडर फ़ाइल में उसका विध्वंसक घोषित है और इसे cpp फ़ाइल में लागू करता है (भले ही कार्यान्वयन खाली हो)।
डेविड स्टोन

क्या मॉड्यूल इसे ठीक करेंगे?
ट्रेवर हिक्की

@TrevorHickey पुरानी टिप्पणी मुझे पता है, लेकिन इसका जवाब देने के लिए वैसे भी। मॉड्यूल निर्भरता को दूर नहीं करेंगे, लेकिन प्रदर्शन लागत के संदर्भ में निर्भरता को बहुत सस्ता सहित बनाना चाहिए। इसके अलावा, यदि मॉड्यूल से सामान्य स्पीडअप आपके संकलित समय को स्वीकार्य सीमा में लाने के लिए पर्याप्त होगा, तो यह अब कोई मुद्दा नहीं है।
ऐड़ीयाकापी

78

प्रस्तावना

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

बाद में हम चलते हैं

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

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

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

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

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

इसका newC # और Java में मौजूद सिंटैक्स से कोई लेना-देना नहीं है । उनका उपयोग पूरी तरह से अलग उद्देश्यों के लिए किया जाता है।

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

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];

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

क्योंकि ढेर ढेर की तुलना में बहुत बड़ा है, कोई मनमाने ढंग से आवंटित कर सकता है / उतनी ही मेमोरी आवंटित कर सकता है जितनी उसे जरूरत है, जबकि स्टैक की एक सीमा है।

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

यह कैसा लाभ है जो आप पूछें? एक बार जब आप सरणियों और बिंदुओं के पीछे भ्रम / मिथक को समझ लेंगे तो उत्तर स्पष्ट हो जाएगा। यह आमतौर पर माना जाता है कि वे समान हैं, लेकिन वे नहीं हैं। यह मिथक इस तथ्य से आता है कि बिंदुओं को सरणियों की तरह ही सब्सक्राइब किया जा सकता है और क्योंकि फ़ंक्शन घोषणा में शीर्ष स्तर पर संकेत करने के लिए सरणियों के क्षय के कारण। हालाँकि, एक बार जब कोई पॉइंटर पॉइंटर पर जाता है, तो पॉइंटर अपनी 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कीवर्ड का उपयोग करके वस्तुओं को दूसरे के रूप में व्यवहार करने की अनुमति देती हैं । इसलिए अगर कोई किसी Entityवस्तु को एक वस्तु के रूप में मानना ​​चाहता है, तो कोई 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अनिवार्य रूप से जाँच के लिए एक शॉर्टहैंड है यदि testNULL या अमान्य पॉइंटर है, जिसका अर्थ है कि कलाकार विफल हो गया है।

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

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

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

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

1. (एन) आरवीओ - एका, (नामांकित) रिटर्न वैल्यू ऑप्टिमाइज़ेशन

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

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


"तो परीक्षण अनिवार्य रूप से जाँच के लिए एक आशुलिपि है अगर परीक्षण NULL या अमान्य सूचक है, जिसका अर्थ है कि कलाकार विफल हो गया है।" मुझे लगता है कि स्पष्टता के लिए इस वाक्य को फिर से लिखना चाहिए।
बर्कस

4
"जावा प्रचार मशीन आपको विश्वास दिलाना चाहेगी" - शायद 1997 में, लेकिन यह अब ऐक्र्रोनिस्टिक है, 2014 में जावा की C ++ से तुलना करने की प्रेरणा नहीं है।
मैट आर

15
पुराना सवाल है, लेकिन कोड सेगमेंट में { std::string* s = new std::string; } delete s; // destructor called.... निश्चित रूप से यह deleteकाम नहीं करेगा क्योंकि कंपाइलर को पता नहीं चलेगा कि sअब क्या है?
बेजर 5000

2
मैं -1 नहीं दे रहा हूं, लेकिन मैं लिखित बयानों से असहमत हूं। सबसे पहले, मैं असहमत हूं कि कोई "प्रचार" है - शायद Y2K के आसपास था, लेकिन अब दोनों भाषाओं को अच्छी तरह से समझा जाता है। दूसरा, मैं तर्क दूंगा कि वे काफी हद तक समान हैं - C ++ सिमूला के साथ विवाहित C का बच्चा है, जावा वर्चुअल मशीन, गारबेज कलेक्टर और HEAVILY सुविधाओं में कटौती करता है, और C # स्ट्रीमलाइन और जावा में गुम सुविधाओं को फिर से बताता है। हां, यह पैटर्न और वैध उपयोग को अलग-अलग बनाता है, लेकिन सामान्य बुनियादी ढांचे / desing को समझना फायदेमंद है ताकि किसी को अंतर दिखाई दे।
गेरासिमोस आर

1
@ जेम्स मटका: आप निश्चित रूप से सही हैं कि स्मृति स्मृति है, और वे दोनों एक ही भौतिक मेमोरी से आवंटित किए गए हैं, लेकिन एक बात पर विचार करना है कि स्टैक आवंटित वस्तुओं के साथ काम करना बेहतर प्रदर्शन विशेषताओं को प्राप्त करना बहुत आम है क्योंकि स्टैक - या कम से कम इसके उच्चतम स्तरों पर - कैश में "गर्म" होने का एक बहुत ही उच्च मौका है क्योंकि फ़ंक्शन दर्ज और बाहर निकलते हैं, जबकि हीप का ऐसा लाभ नहीं होता है, इसलिए यदि आप हीप में पॉइंटर का पीछा कर रहे हैं तो आपको कई कैश मिस मिल सकते हैं जो आप की संभावना ढेर पर नहीं होगा। लेकिन यह सब "यादृच्छिकता" सामान्य रूप से स्टैक के पक्ष में है।
ग्यारसीमोस आर

23

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

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

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

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

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

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

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

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

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

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

उदाहरण:

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

1
std::auto_ptrपदावनत किया जाता है, कृपया इसका उपयोग न करें।
नील

2
बहुत यकीन है कि आप एक सदस्य के रूप में एक संदर्भ सूची के साथ एक आरंभीकरण सूची प्रदान किए बिना एक संदर्भ के रूप में नहीं कर सकते। (एक संदर्भ को तुरंत आरंभीकृत किया जाना है। यहां तक ​​कि कंस्ट्रक्टर बॉडी को सेट करने के लिए बहुत देर हो चुकी है, IIRC।)
cHao

20

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

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


6
Object objहमेशा स्टैक पर नहीं होता है - उदाहरण के लिए ग्लोबल्स या सदस्य चर।
तेनूर

2
@LightnessRacesinOrbit मैंने केवल एक ब्लॉक में आवंटित वस्तुओं के बारे में उल्लेख किया है, न कि वैश्विक और सदस्य चर के बारे में। बात यह है कि यह स्पष्ट नहीं था, अब इसे सही किया - जवाब में "एक ब्लॉक के भीतर" जोड़ा। आशा है कि अब इसकी गलत जानकारी नहीं है :)
कार्तिक कल्याणसुंदरम

20

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

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

Object myObject;

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

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

 Object *myObject = new Object;

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

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

C ++ 11 के बाद से आप गतिशील आवंटन के सुरक्षित तरीकों का उपयोग कर सकते हैं: new Objectशेयर्स_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"); 

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


1
then myObject will not get destroyed once function endsयह बिल्कुल होगा।
ऑर्बिट

6
पॉइंटर मामले में, myObjectअभी भी नष्ट हो जाएगा, बस किसी अन्य स्थानीय चर के रूप में। अंतर यह है कि इसका मान किसी वस्तु का सूचक है, वस्तु का नहीं, और गूंगा सूचक का विनाश इसके सूचक को प्रभावित नहीं करता है। तो वस्तु कहा बच जाएगी विनाश।
cHao

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

13

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

इसके अलावा आपको यह समझने की ज़रूरत है कि "नया" उस ढेर पर मेमोरी आवंटित करता है जिसे किसी बिंदु पर मुक्त करने की आवश्यकता होती है। जब आपको "नया" उपयोग करने की आवश्यकता नहीं होती है, तो मेरा सुझाव है कि आप "स्टैक पर" एक नियमित ऑब्जेक्ट परिभाषा का उपयोग करें।


6

वैसे मुख्य सवाल यह है कि मुझे ऑब्जेक्ट के बजाय पॉइंटर का उपयोग क्यों करना चाहिए? और मेरा जवाब, आपको (लगभग) वस्तु के बजाय कभी भी सूचक का उपयोग नहीं करना चाहिए, क्योंकि C ++ में संदर्भ हैं , यह तब सुरक्षित है और संकेत के समान प्रदर्शन की गारंटी देता है।

आपके प्रश्न में एक और बात का उल्लेख किया गया है:

Object *myObject = new Object;

यह कैसे काम करता है? यह एक Objectप्रकार का पॉइंटर बनाता है , एक ऑब्जेक्ट को फिट करने के लिए मेमोरी आवंटित करता है और डिफ़ॉल्ट कंस्ट्रक्टर को कॉल करता है, अच्छा लगता है, है ना? लेकिन वास्तव में यह इतना अच्छा नहीं है, अगर आपको डायनामिकली मैमोरी (यूज्ड कीवर्ड new) आवंटित की जाती है, तो आपको मेमोरी को मैन्युअली भी फ्री करना होगा, यानी आपके पास मौजूद कोड में:

delete myObject;

यह विध्वंसक कॉल करता है और मेमोरी को मुक्त करता है, आसान दिखता है, हालांकि बड़ी परियोजनाओं में यह पता लगाना मुश्किल हो सकता है कि एक थ्रेड मेमोरी को मुक्त करता है या नहीं, लेकिन उस उद्देश्य के लिए आप साझा किए गए पॉइंटर्स को आज़मा सकते हैं , ये थोड़ा कम हो जाता है, लेकिन इसके साथ काम करना बहुत आसान है उन्हें।


और अब कुछ परिचय खत्म हो गया है और सवाल पर वापस जाना है।

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

जरा गौर करें, आपके पास std::string(यह भी ऑब्जेक्ट है) और इसमें वास्तव में बहुत अधिक डेटा है, उदाहरण के लिए बड़ा XML, अब आपको इसे पार्स करने की आवश्यकता है, लेकिन इसके लिए आपके पास फ़ंक्शन हैं void foo(...)जिन्हें विभिन्न तरीकों से घोषित किया जा सकता है:

  1. void foo(std::string xml); इस स्थिति में आप अपने वैरिएबल से फंक्शन स्टैक तक सभी डेटा को कॉपी कर लेंगे, इसमें कुछ समय लगेगा, इसलिए आपका प्रदर्शन कम होगा।
  2. void foo(std::string* xml); इस मामले में आप पॉइंटर को ऑब्जेक्ट के लिए पास करेंगे, वैसी ही गति size_tवैरिएबल के रूप में , हालांकि इस घोषणा में त्रुटि प्रवणता है, क्योंकि आप NULLपॉइंटर या अमान्य पॉइंटर पास कर सकते हैं । पॉइंटर्स आमतौर पर इस्तेमाल किया जाता है Cक्योंकि इसमें संदर्भ नहीं होते हैं।
  3. void foo(std::string& xml); यहां आप संदर्भ पास करते हैं, मूल रूप से यह पासिंग पॉइंटर के समान है, लेकिन कंपाइलर कुछ सामान करता है और आप अमान्य संदर्भ नहीं दे सकते हैं (वास्तव में अमान्य संदर्भ के साथ स्थिति बनाना संभव है, लेकिन यह संकलक को धोखा दे रहा है)।
  4. void foo(const std::string* xml); यहाँ दूसरी के समान ही है, बस सूचक मान को बदला नहीं जा सकता है।
  5. void foo(const std::string& xml); यहाँ तीसरे के समान है, लेकिन ऑब्जेक्ट वैल्यू को बदला नहीं जा सकता है।

मैं और क्या उल्लेख करना चाहता हूं, आप डेटा को पारित करने के लिए इन 5 तरीकों का उपयोग कर सकते हैं कोई फर्क नहीं पड़ता कि आपने कौन सा आवंटन तरीका चुना है ( newया नियमित रूप से )।


उल्लेख करने के लिए एक और बात, जब आप नियमित रूप से ऑब्जेक्ट बनाते हैं, तो आप स्टैक में मेमोरी आवंटित करते हैं, लेकिन जब आप इसे बनाते हैं तो newआप ढेर आवंटित करते हैं। यह स्टैक आवंटित करने के लिए बहुत तेज़ है, लेकिन यह डेटा के वास्तव में बड़े सरणियों के लिए एक छोटा सा है, इसलिए यदि आपको बड़ी वस्तु की आवश्यकता है तो आपको ढेर का उपयोग करना चाहिए, क्योंकि आपको स्टैक ओवरफ्लो हो सकता है, लेकिन आमतौर पर एसटीएल कंटेनरों का उपयोग करके इस मुद्दे को हल किया जाता है और याद रखें std::stringकंटेनर भी है, कुछ लोग इसे भूल गए :)


5

मान लीजिए कि आपके पास class Aवह सम्‍मिलित है, class Bजब आप class Bबाहर के किसी समारोह को बुलाना चाहते हैं, तो आप class Aबस इस वर्ग को एक संकेतक प्राप्त करेंगे और आप जो चाहें कर सकते हैं और यह class Bआपके संदर्भ को भी बदल देगाclass A

लेकिन गतिशील वस्तु से सावधान रहें


5

ऑब्जेक्ट के लिए पॉइंटर्स का उपयोग करने के कई फायदे हैं -

  1. दक्षता (जैसा कि आपने पहले ही बताया)। कार्यों के लिए वस्तुओं को पास करने का मतलब है वस्तु की नई प्रतियां बनाना।
  2. तीसरे पक्ष के पुस्तकालयों से वस्तुओं के साथ काम करना। यदि आपका ऑब्जेक्ट किसी थर्ड पार्टी कोड से संबंधित है और लेखक केवल पॉइंटर्स (कोई कॉपी कंस्ट्रक्टर आदि) के माध्यम से अपनी वस्तुओं के उपयोग का इरादा नहीं रखते हैं, तो आप इस ऑब्जेक्ट के चारों ओर से गुजर सकते हैं। मूल्य से गुजरने से समस्या हो सकती है। (डीप कॉपी / उथले कॉपी मुद्दे)।
  3. यदि वस्तु एक संसाधन का मालिक है और आप चाहते हैं कि स्वामित्व अन्य वस्तुओं के साथ सुरक्षित न हो।

3

यह लंबाई पर चर्चा की गई है, लेकिन जावा में सब कुछ एक सूचक है। यह स्टैक और हीप एलोकेशन के बीच कोई अंतर नहीं करता है (सभी ऑब्जेक्ट्स को हीप पर आवंटित किया जाता है), इसलिए आपको ऐसा नहीं लगता कि आप पॉइंटर्स का उपयोग कर रहे हैं। C ++ में, आप अपनी मेमोरी आवश्यकताओं के आधार पर दोनों को मिला सकते हैं। C ++ (duh) में प्रदर्शन और मेमोरी का उपयोग अधिक निर्धारक है।


3
Object *myObject = new Object;

ऐसा करने से ऑब्जेक्ट (हीप पर) का संदर्भ बन जाएगा जिसे मेमोरी लीक से बचने के लिए स्पष्ट रूप से हटाना होगा

Object myObject;

ऐसा करने से स्वचालित प्रकार का एक ऑब्जेक्ट (myObject) बनेगा ( स्टैक पर) जो ऑब्जेक्ट (myObject) के दायरे से बाहर जाने पर स्वचालित रूप से हटा दिया जाएगा।


1

एक पॉइंटर सीधे किसी ऑब्जेक्ट की मेमोरी लोकेशन को संदर्भित करता है। जावा के पास ऐसा कुछ नहीं है। जावा में संदर्भ होते हैं जो हैश टेबल के माध्यम से ऑब्जेक्ट के स्थान को संदर्भित करते हैं। आप इन संदर्भों के साथ जावा में सूचक अंकगणित जैसा कुछ नहीं कर सकते।

आपके प्रश्न का उत्तर देने के लिए, यह केवल आपकी प्राथमिकता है। मैं जावा-जैसे सिंटैक्स का उपयोग करना पसंद करता हूं।


हैश टेबल? हो सकता है कि कुछ JVM में हो, लेकिन उस पर भरोसा न करें।
ज़ेन लिंक्स

जावा के साथ आने वाले जेवीएम के बारे में क्या? बेशक आप कुछ भी लागू कर सकते हैं आप जेवीएम की तरह सोच सकते हैं जो सीधे पॉइंटर्स का उपयोग करता है या पॉइंटर गणित करता है। यह कहने जैसा है कि "लोग सामान्य ठंड से नहीं मरते हैं" और एक प्रतिक्रिया मिल रही है "हो सकता है कि ज्यादातर लोग इस पर भरोसा न करें!" हा हा!
रियोरिको

2
@RioRicoRick HotSpot जावा संदर्भों को मूल बिंदुओं के रूप में कार्यान्वित करता है, देखें docs.oracle.com/javase/7/docs/technotes/guides/vm/… जहाँ तक मैं देख सकता हूँ, JRocker वही करता है। वे दोनों OOP संपीड़न का समर्थन करते हैं, लेकिन कभी भी हैश-टेबल का उपयोग नहीं करते हैं। प्रदर्शन के परिणाम शायद विनाशकारी होंगे। इसके अलावा, "यह सिर्फ आपकी प्राथमिकता है" इसका अर्थ यह लगता है कि दोनों समान व्यवहार के लिए केवल अलग-अलग वाक्यविन्यास हैं, जो निश्चित रूप से वे नहीं हैं।
अधिकतम बराकॉल्फ


0

संकेत के साथ ,

  • सीधे मेमोरी से बात कर सकते हैं।

  • एक प्रोग्राम के बहुत सारे मेमोरी लीक को पॉइंटर्स से जोड़कर रोका जा सकता है।


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

0

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


0

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


0

मैं सूचक का एक महत्वपूर्ण उपयोग मामला शामिल करूंगा। जब आप बेस क्लास में कुछ ऑब्जेक्ट स्टोर कर रहे हैं, लेकिन यह पॉलीमॉर्फिक हो सकता है।

Class Base1 {
};

Class Derived1 : public Base1 {
};


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

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

तो इस मामले में आप एक प्रत्यक्ष वस्तु के रूप में bObj की घोषणा नहीं कर सकते, आपके पास सूचक होना चाहिए।


-5

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


14
आपको संदर्भ से गुजरना पसंद करना चाहिए
bolov

2
मेरे पास वेरिएबल के लिए कंटीन्यू-रेफरेंस से गुजरने की सलाह std::string test;है, void func(const std::string &) {}लेकिन जब तक फंक्शन को इनपुट में बदलाव की जरूरत नहीं पड़ती है, तब तक मैं पॉइंटर्स का उपयोग करने की सलाह देता हूं (ताकि कोड पढ़ने वाला कोई भी व्यक्ति नोटिस करे &, और फंक्शन के इनपुट को समझ सके)
टॉप-

-7

कई बेहतरीन जवाब पहले से ही हैं, लेकिन मैं आपको एक उदाहरण देता हूं:

मेरे पास एक साधारण आइटम वर्ग है:

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

मैं उनमें से एक गुच्छा रखने के लिए एक वेक्टर बनाता हूं।

std::vector<Item> inventory;

मैं एक मिलियन आइटम ऑब्जेक्ट बनाता हूं, और उन्हें वेक्टर पर वापस धकेलता हूं। मैं नाम से वेक्टर को सॉर्ट करता हूं, और फिर एक विशेष आइटम नाम के लिए एक सरल पुनरावृत्त बाइनरी खोज करता हूं। मैं कार्यक्रम का परीक्षण करता हूं, और निष्पादन को समाप्त करने में 8 मिनट से अधिक समय लगता है। फिर मैं अपनी इन्वेंट्री वेक्टर को ऐसे बदलता हूं:

std::vector<Item *> inventory;

... और नए के माध्यम से मेरी मिलियन आइटम ऑब्जेक्ट बनाएं। केवल मैं अपने कोड में परिवर्तन करता हूं, आइटम को पॉइंटर्स का उपयोग करने के लिए हूं, अंत में मेमोरी क्लीनअप के लिए एक लूप को छोड़कर। वह कार्यक्रम 40 सेकंड से कम समय में चलता है, या 10x की गति से बेहतर होता है। संपादित करें: कोड http://pastebin.com/DK24SPeW पर है। कंपाइलर ऑप्टिमाइज़ेशन के साथ यह मशीन पर केवल 3.4 गुना वृद्धि दिखाता है जिसे मैंने अभी इसका परीक्षण किया है, जो अभी भी काफी है।


2
तो क्या आप संकेत की तुलना कर रहे हैं या फिर आप वास्तविक वस्तुओं की तुलना करते हैं? मुझे बहुत संदेह है कि परोक्ष का एक और स्तर प्रदर्शन में सुधार कर सकता है। कृपया कोड प्रदान करें! क्या आप बाद में ठीक से सफाई करते हैं?
स्टीफन

1
@stefan मैं सॉर्ट और खोज दोनों के लिए ऑब्जेक्ट्स के डेटा (विशेष रूप से, नाम फ़ील्ड) की तुलना करता है। मैं ठीक से सफाई करता हूं, जैसा कि मैंने पहले ही पोस्ट में उल्लेख किया है। स्पीडअप संभवतः दो कारकों के कारण होता है: 1) एसटीडी :: वेक्टर पुश_बैक () ऑब्जेक्ट्स को कॉपी करता है, इसलिए पॉइंटर संस्करण को केवल प्रति ऑब्जेक्ट एक सिंगल पॉइंटर कॉपी करने की आवश्यकता होती है। इससे प्रदर्शन पर कई प्रभाव पड़ते हैं, क्योंकि न केवल कम डेटा कॉपी किया जाता है, बल्कि वेक्टर क्लास मेमोरी एलोकेटर कम पिटाई होती है।
डैरेन

2
यहाँ कोड आपके उदाहरण के लिए व्यावहारिक रूप से कोई अंतर नहीं दिखा रहा है: छँटाई। पॉइंटर कोड अकेले सॉर्ट के लिए नॉन-पॉइंटर कोड की तुलना में 6% तेज है, लेकिन कुल मिलाकर यह नॉन-पॉइंटर कोड की तुलना में 10% धीमा है। ideone.com/G0c7zw
स्टीफन

3
कुंजी शब्द push_back:। बेशक यह प्रतियां। आपको emplaceअपनी वस्तुओं को बनाते समय इन-प्लेस होना चाहिए था (जब तक कि आपको उन्हें कहीं और कैश करने की आवश्यकता न हो)।
अंडरस्कोर_ड

1
संकेत के क्षेत्र लगभग हमेशा गलत होते हैं। कृपया उन्हें विस्तार से बताए बिना सलाह न दें, विवरण और नियम और विपक्ष। आपको एक प्रो मिला है, जो केवल एक खराब कोडेड काउंटर-उदाहरण का परिणाम है, और इसे गलत तरीके से पेश किया गया है
हल्बीनेस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.