2010-12-10 8 views
9

मुझे संदर्भ समझने में समय का शैतान है। निम्नलिखित कोड पर विचार करें:सीखना सी ++: रिटर्निंग संदर्भ और स्लाइसिंग के आसपास हो रही है

class Animal 
{ 
public: 
    virtual void makeSound() {cout << "rawr" << endl;} 
}; 

class Dog : public Animal 
{ 
public: 
    virtual void makeSound() {cout << "bark" << endl;} 
}; 

Animal* pFunc() 
{ 
    return new Dog(); 
} 

Animal& rFunc() 
{ 
    return *(new Dog()); 
} 

Animal vFunc() 
{ 
    return Dog(); 
} 

int main() 
{ 
    Animal* p = pFunc(); 
    p->makeSound(); 

    Animal& r1 = rFunc(); 
    r1.makeSound(); 

    Animal r2 = rFunc(); 
    r2.makeSound(); 

    Animal v = vFunc(); 
    v.makeSound(); 
} 

और परिणाम हैं: "छाल छाल कच्चे कच्चे"।

सोचने के जावा तरीके में, (जिसने सी ++ की मेरी अवधारणा को स्पष्ट रूप से दूषित कर दिया है), परिणाम "छाल छाल छाल छाल" होंगे। मैं अपने previous question से समझता हूं कि यह अंतर स्लाइसिंग के कारण है और अब मुझे स्लाइसिंग की अच्छी समझ है।

लेकिन मान लें कि मुझे ऐसा फ़ंक्शन चाहिए जो एक पशु मूल्य देता है जो वास्तव में एक कुत्ता है।

  1. क्या मैं सही ढंग से समझता हूं कि मुझे जो निकटतम मिल सकता है वह संदर्भ है?
  2. इसके अलावा, क्या यह आरएफएनसी इंटरफ़ेस का उपयोग करके उस पर निर्भर है कि यह संदर्भ देखने के लिए एक पशु & असाइन किया गया है? (या अन्यथा जानबूझकर एक पशु के संदर्भ को असाइन करें, जो स्लाइसिंग के माध्यम से, पॉलिमॉर्फिज्म को छोड़ देता है।)
  3. पृथ्वी पर कैसे मैं एक नई जेनरेट की गई वस्तु के संदर्भ को आरएफएनसी में उपरोक्त बेवकूफ चीज़ के बिना संदर्भ वापस कर रहा हूं? (कम से कम मैंने सुना है इस बेवकूफ है।)

अद्यतन: हर किसी को अब तक इस बात से सहमत करने लगता है के बाद से है कि rFunc यह नाजायज, कि एक और संबंधित प्रश्नों को लाता है:

अगर मैं वापस पारित एक सूचक मैं प्रोग्रामर से कैसे संवाद कर सकता हूं कि अगर यह मामला है तो पॉइंटर को हटाना नहीं है? या वैकल्पिक रूप से मैं कैसे संवाद कर सकता हूं कि पॉइंटर किसी भी समय (उसी धागे से लेकिन एक अलग फ़ंक्शन से) हटाने के अधीन है ताकि कॉलिंग फ़ंक्शन इसे स्टोर न करे, अगर ऐसा होता है। टिप्पणियों के माध्यम से संवाद करने का एकमात्र तरीका है? वह मैला लगता है।

नोट: यह सब एक टेम्पलेटेड shared_pimpl अवधारणा के लिए एक विचार के लिए अग्रणी है जिस पर मैं काम कर रहा था। उम्मीद है कि मैं इसके बारे में कुछ दिनों में कुछ पोस्ट करने के लिए पर्याप्त सीखूंगा।

+3

जावा संदर्भ सी ++ पॉइंटर्स की तरह अधिक हैं। जावा में सी ++ संदर्भों की तरह कुछ भी नहीं है। –

+0

कृपया प्रभावी सी ++ पढ़ें। –

+0

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

उत्तर

5

1) आप नई वस्तुओं बना रहे हैं, तो आप कभी नहीं एक संदर्भ (# 3 पर अपने खुद के टिप्पणी देखें।) आप एक सूचक (संभवतः std::shared_ptr या std::auto_ptr से लिपटे लौट सकते हैं) वापस जाने के लिए चाहते हैं। (आप प्रतिलिपि से भी वापस आ सकते हैं, लेकिन यह new ऑपरेटर का उपयोग करने में असंगत है; यह पॉलिमॉर्फिज्म के साथ थोड़ा असंगत है।)

2) rFunc बस गलत है। ऐसा मत करो। यदि आपने ऑब्जेक्ट बनाने के लिए new का उपयोग किया है, तो इसे एक (वैकल्पिक रूप से लपेटा गया) पॉइंटर के माध्यम से वापस कर दें।

3) आपको नहीं माना जाता है। यही पॉइंटर्स के लिए हैं।


संपादित (अपने अद्यतन का जवाब :) यह परिदृश्य आप का वर्णन कर रहे हैं चित्र करना मुश्किल है। क्या यह कहना अधिक सटीक होगा कि कॉलर कुछ अन्य (विशिष्ट) विधि को कॉल करने के बाद लौटाया गया पॉइंटर अमान्य हो सकता है?

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

+0

पॉइंटर एफटीडब्ल्यू। वे निश्चित रूप से किसी मान में परिवर्तित नहीं होते हैं, इसलिए आपके कॉलर को संभावित बहुरूपता के बारे में चेतावनी दी जाती है। –

+0

आप मेरे नए प्रश्न में रुचि हो सकती है: http://stackoverflow.com/questions/4410790/c-creating-a-shared-object-rather-than-a-shared-pointer-to-an-object – JnBrymn

1

आदेश टुकड़ा करने की क्रिया आप वापसी या एक सूचक वस्तु के आसपास पारित करने के लिए है से बचने के लिए। (ध्यान दें कि एक संदर्भ मूल रूप से एक 'स्थायी रूप से dereferenced सूचक' है।

Animal r2 = rFunc(); 
r2.makeSound(); 

यहाँ, r2 टिंग instantiated (संकलक उत्पन्न प्रतिलिपि ctor का प्रयोग करके), लेकिन यह कुत्ता भागों है छोड़ना रहा है। आप इसे इस तरह करते हैं टुकड़ा करने की क्रिया घटित नहीं होगा:

Animal& r2 = rFunc(); 

हालांकि अपने vFunc() फ़ंक्शन स्लाइस विधि के अंदर ही

मैं भी इस समारोह में उल्लेख करेंगे:।

Animal& rFunc() 
{ 
    return *(new Dog()); 
} 

यह अजीब और असुरक्षित है; आप एक अस्थायी अनामित चर (dereferenced कुत्ता) का संदर्भ बना रहे हैं। पॉइंटर वापस करने के लिए यह अधिक उपयुक्त है। रिटर्निंग संदर्भ आमतौर पर सदस्य चर को वापस करने के लिए उपयोग किया जाता है और इसी तरह।

+0

आप अभी भी विभाजित हो सकते हैं एक सूचक के साथ: पशु * pnimal = * कुत्ता; विभाजन का कारण बन जाएगा। –

+0

* कुत्ता पॉइंटर को कम करेगा। – seand

+0

तो क्या यह जवाब भ्रामक है? http: // stackoverflow।कॉम/ए/3835757/1538531 क्योंकि जब आपको बहुलक रूप से लौटने की आवश्यकता होती है, तो आप एक सूचक वापस लौटने के लिए नहीं मिल सकते हैं। – Derek

0

आपका बंटवारे समस्याओं चली पशु एक सार आधार वर्ग है (मैं गतिशील स्मृति मेमोरी लीक के कारण ... संदर्भ में जाने के साथ अपनी समस्याओं की अनदेखी कर रहा हूँ)। इसका मतलब है कि इसमें कम से कम एक शुद्ध वर्चुअल विधि है और इसे तुरंत चालू नहीं किया जा सकता है। निम्नलिखित एक संकलक त्रुटि हो जाता है:

Animal a = rFunc(); // a cannot be directly instantiated 
         // spliting prevented by compiler! 

लेकिन संकलक अनुमति देता है:

Animal* a = pFunc(); // polymorphism maintained! 
Animal& a = rFunc(); // polymorphism maintained! 

इस प्रकार संकलक दिन बचाता है!

+0

एर, नहीं। 'Animal & a = rFunc()' के मामले में, जब आप इसके साथ काम करते हैं तो आप ऑब्जेक्ट को कैसे हटाते हैं? –

+0

@Dan मुझे पता है कि, * बंटवारे * समस्या दूर हो जाने ... नहीं स्मृति रिसाव :) –

+0

ठीक है, लेकिन प्रोत्साहित नहीं करते newbies ;-) –

0

प्वाइंट 1: संदर्भों का उपयोग न करें। पॉइंटर्स का प्रयोग करें।

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

आप एक रिश्ता है, जैसे

आभासी bool पशु :: खाती (पशु * अन्य) = 0 को लागू करने का प्रयास करें;

क्या आप यह नहीं कर सकते हैं। बात यह है कि: कुत्ता पशु अमूर्तता का उपप्रकार नहीं है। Taxonomies का पूरा बिंदु यह है कि विभाजन के प्रत्येक स्तर के वर्गों में एक दिलचस्प गुण है।

उदाहरण के लिए: रीढ़ एक रीढ़ है और हम पूछ सकते हैं यह cartiledge या हड्डी से बना है कि क्या .. हम भी नहीं अकशेरुकी के उस सवाल पूछ सकते हैं।

पूरी तरह से समझने के लिए आपको देखना होगा कि आप एक कुत्ता वस्तु नहीं कर सकता।आखिरकार, यह एक अमूर्त है, है ना? क्योंकि, केल्पी और कोलीज़ हैं, और एक व्यक्तिगत कुत्ते को कुछ प्रजातियों का होना चाहिए .. वर्गीकरण योजना जितनी गहरी हो सकती है उतनी गहरी हो सकती है लेकिन यह कभी भी किसी भी ठोस व्यक्तियों का समर्थन नहीं कर सकती है। फिडो गैर-कुत्ते है, यह सिर्फ उसका वर्गीकरण टैग है।

2

अपने प्रश्न के दूसरे भाग का उत्तर देने के ("मैं कैसे संवाद है कि सूचक किसी भी समय हटा दिए जाएंगे कि") -

यह एक खतरनाक बात है, और सूक्ष्म विवरण आप पर विचार करना होगा है । यह प्रकृति में उग्र है।

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

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

छद्म कोड (आविष्कार कमजोर और साझा संकेत के आधार पर, मैं बूस्ट का उपयोग नहीं कर रहा हूँ ...) -

weak<Animal> animalWeak = getAnimalThatMayDisappear(); 
// ... 
{ 
    shared<Animal> animal = animalWeak.getShared(); 
    if (animal) 
    { 
     // 'animal' is still valid, use it. 
     // ... 
    } 
    else 
    { 
     // 'animal' is not valid, can't use it. It points to NULL. 
     // Now what? 
    } 
} 
// And at this point the shared pointer of 'animal' is implicitly released. 

लेकिन इस जटिल और त्रुटियों की संभावना है, और संभावना अपने जीवन कठिन हो जाएगा। यदि संभव हो तो मैं सरल डिजाइन के लिए जाने की सलाह दूंगा।

+0

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

1

If I pass back a pointer how do I communicate to the programmer that the pointer is not theirs to delete if this is the case? Or alternatively how do I communicate that the pointer is subject to deletion at any time (from the same thread but a different function) so that the calling function should not store it, if this is the case.

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

अन्यथा, उचित अर्थशास्त्र के साथ एक स्मार्ट सूचक का उपयोग करने का प्रयास करें। कोई भी कभी सोचा नहीं होगा कि delete &*some_boost_shared_ptr; एक अच्छा विचार है।

1

But let's say that I want a function that returns an Animal value that is really a Dog.

  1. Do I understand correctly that the closest that I can get is a reference?

हाँ, आप सही हैं। लेकिन मुझे लगता है कि समस्या इतना नहीं है कि आप संदर्भों को समझ नहीं पाते हैं, लेकिन आप सी ++ में विभिन्न प्रकार के चर या new सी ++ में काम नहीं करते हैं। सी ++ में, वेरिएबल एक आदिम डेटा (int, float, double, etc।), एक ऑब्जेक्ट, या पॉइंटर/एक आदिम और/या ऑब्जेक्ट के संदर्भ हो सकते हैं। जावा में, चर केवल एक आदिम या किसी ऑब्जेक्ट का संदर्भ हो सकता है।

सी ++ में, जब आप एक चर घोषित करते हैं, वास्तविक स्मृति आवंटित होती है और चर के साथ जुड़ी होती है। जावा में, आपको स्पष्ट रूप से नए का उपयोग करके ऑब्जेक्ट्स बनाना होगा और एक ऑब्जेक्ट को नई ऑब्जेक्ट को स्पष्ट रूप से असाइन करना होगा। हालांकि यहां मुख्य बिंदु यह है कि, सी ++ में, ऑब्जेक्ट और वेरिएबल जो आप एक्सेस करने के लिए उपयोग करते हैं, वैसा ही नहीं है जब वेरिएबल एक पॉइंटर या संदर्भ होता है। Animal a; का मतलब Animal *a; से कुछ अलग है जिसका अर्थ Animal &a; से कुछ अलग है। इनमें से कोई भी संगत प्रकार नहीं है, और वे अंतर-परिवर्तनीय नहीं हैं।

जब आप टाइप करते हैं, Animal a1 सी ++ में टाइप करें। एक नया Animal ऑब्जेक्ट बनाया गया है। इसलिए, जब आप Animal a2 = a1; टाइप करते हैं, तो आप स्मृति में विभिन्न स्थानों पर दो चर (a1 और a2) और दो Animal ऑब्जेक्ट्स के साथ समाप्त होते हैं।दोनों वस्तुओं का एक ही मूल्य होता है, लेकिन यदि आप चाहें तो आप स्वतंत्र रूप से अपने मूल्यों को बदल सकते हैं। जावा में, यदि आपने एक ही सटीक कोड टाइप किया है, तो आप दो चर के साथ समाप्त हो जाएंगे, लेकिन केवल एक ऑब्जेक्ट। जब तक आप किसी भी चर को पुन: असाइन नहीं करते थे, तब तक उनके पास हमेशा एक ही मूल्य होगा।

  1. Furthermore, is it incumbent upon the one using the rFunc interface to see that the reference returned is assign an Animal&? (Or otherwise intentionally assign the reference to an Animal which, via slicing, discards polymorphism.)

जब आप संदर्भ और संकेत का उपयोग करें, आप जहां आप इसे उपयोग करना चाहते हैं उसकी प्रतिलिपि बनाना बिना एक वस्तु के मूल्य का उपयोग कर सकते हैं। इससे आप घुंघराले ब्रेसिज़ के बाहर से इसे बदल सकते हैं जहां आपने वस्तु को अस्तित्व में घोषित कर दिया था। संदर्भ आमतौर पर फ़ंक्शन पैरामीटर के रूप में उपयोग किए जाते हैं या किसी ऑब्जेक्ट के निजी डेटा सदस्यों को उनकी एक नई प्रतिलिपि के बिना वापस करने के लिए उपयोग किए जाते हैं। आम तौर पर, जब आप एक संदर्भ प्राप्त करते हैं, तो आप इसे किसी भी चीज़ को असाइन नहीं करते हैं। अपने उदाहरण का उपयोग करके, संदर्भ को rFunc() द्वारा एक चर के लिए वापस करने के बजाय, आमतौर पर rFunc().makeSound(); टाइप करेगा।

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

  1. How on earth am I supposed to return a reference to a newly generated object without doing the stupid thing I did above in rFunc? (At least I've heard this is stupid.)

समस्या यह है कि आप एक वस्तु को तीन तरीकों से बना सकते हैं।

{ // the following expressions evaluate to ... 
Animal local; 
// an object that will be destroyed when control exits this block 
Animal(); 
// an unamed object that will be destroyed immediately if not bound to a reference 
new Animal(); 
// an unamed Animal *pointer* that can't be deleted unless it is assigned to a Animal pointer variable. 
{ 
    // doing other stuff 
} 
} // <- local destroyed 

सभी new C++ करता स्मृति जहां इसे नष्ट नहीं किया जाएगा में वस्तुओं को बनाने के लिए जब तक आप ऐसा कहते है। लेकिन, इसे नष्ट करने के लिए, आपको याद रखना होगा कि यह स्मृति में कहां बनाया गया था। आप पॉइंटर वैरिएबल, Animal *AnimalPointer;, बनाकर और new Animal() द्वारा AnimalPointer = new Animal(); पर लौटने वाले पॉइंटर को असाइन करके ऐसा करते हैं। जब आप इसके साथ काम करते हैं तो Animal ऑब्जेक्ट को नष्ट करने के लिए, आपको delete AnimalPointer; टाइप करना होगा।

0

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

संबंधित मुद्दे