2009-02-26 10 views
5

यह एक स्मृति आवंटन मुद्दा है जिसे मैंने वास्तव में कभी नहीं समझा है।संदर्भ द्वारा पारित करने का अच्छा विचार कब नहीं है?

 
void unleashMonkeyFish() 
{ 
    MonkeyFish * monkey_fish = new MonkeyFish(); 
    std::string localname = "Wanda"; 
    monkey_fish->setName(localname); 
    monkey_fish->go(); 
} 

उपरोक्त कोड में, मैं ढेर पर एक MonkeyFish वस्तु बना लिया है, यह एक नाम सौंपा है, और फिर दुनिया पर यह फैलाया। आइए मान लें कि आवंटित स्मृति का स्वामित्व बंदरफिश ऑब्जेक्ट में ही स्थानांतरित कर दिया गया है - और केवल बंदरफिश ही तय करेगा कि मरने और खुद को हटाने के लिए क्या होगा।

अब

, जब मैं MonkeyFish वर्ग के अंदर "नाम" डेटा सदस्य परिभाषित करते हैं, मैं निम्न में से एक चुन सकते हैं:

 
std::string name; 
std::string & name; 

जब मैं MonkeyFish वर्ग के अंदर setName() फ़ंक्शन के लिए प्रोटोटाइप को परिभाषित , मैं निम्नलिखित में से एक चुन सकता हूं:

 
void setName(const std::string & parameter_name); 
void setName(const std::string parameter_name); 

मैं स्ट्रिंग प्रतियों को कम करने में सक्षम होना चाहता हूं। असल में, अगर मैं कर सकता हूं तो मैं उन्हें पूरी तरह खत्म करना चाहता हूं। तो, ऐसा लगता है कि मुझे संदर्भ द्वारा पैरामीटर पास करना चाहिए ... सही?

मुझे क्या बग है कि ऐसा लगता है कि मेरा स्थानीय नाम परिवर्तक अनलैशमोनकीफिश() फ़ंक्शन पूरा होने के बाद गुंजाइश से बाहर जा रहा है। क्या इसका मतलब है कि मुझे कॉपी द्वारा पैरामीटर पास करने के लिए मजबूर किया गया है? या मैं इसे संदर्भ से पास कर सकता हूं और किसी भी तरह से "इससे दूर हो सकता हूं"?

असल में, मैं इन स्थितियों से बचना चाहते हैं:

  1. मैं केवल localname स्ट्रिंग के लिए स्मृति के लिए दूर जाना जब unleashMonkeyFish() फ़ंक्शन समाप्त हो जाता है MonkeyFish का नाम स्थापित करने के लिए, नहीं करना चाहती। (ऐसा लगता है कि यह बहुत खराब होगा।)
  2. यदि मैं इसकी सहायता कर सकता हूं तो मैं स्ट्रिंग की प्रतिलिपि बनाना नहीं चाहता हूं।
  3. मैं नए localname

मैं क्या प्रोटोटाइप और डेटा सदस्य संयोजन का उपयोग करना चाहिए के लिए नहीं पसंद करेंगे?

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

संपादित: ग्रेग Hewgil ने कहा है कि यह एक संदर्भ के रूप में नाम चर स्टोर करने के लिए अवैध है, चूंकि इसे कन्स्ट्रक्टर में सेट नहीं किया जा रहा है। मैं इस सवाल में गलती छोड़ रहा हूं, क्योंकि मुझे लगता है कि मेरी गलती (और ग्रेग का सुधार) पहली बार इस समस्या को देखने वाले किसी के लिए उपयोगी हो सकता है।

+0

क्या आपने प्रोफाइल किया है और यह निर्धारित किया है कि यह एक समस्या है? – CTT

+0

@ सीटीटी - नहीं, मैंने नहीं किया है। हालांकि, अगर मैं इससे बच सकता हूं तो स्ट्रिंग की प्रतिलिपि बनाने में अक्षम लगता है। आम तौर पर, मैं जितना संभव हो कॉपी करने से बचना चाहता हूं। – Runcible

+0

"monkey_fish" ढेर पर है, ढेर नहीं। –

उत्तर

6

एक तरीका यह है करने के लिए अपने स्ट्रिंग

std::string name; 

अपने वस्तु के डेटा-सदस्य के रूप में है।और फिर, unleashMonkeyFish समारोह में एक स्ट्रिंग बनाने की तरह तुमने किया था, और संदर्भ द्वारा इसे पारित की तरह आप

void setName(const std::string & parameter_name) { 
    name = parameter_name; 
} 

से पता चला है यह तुम क्या चाहते हो जाएगा - एक प्रतिलिपि बनाने के लिए अपने डेटा सदस्यीय में स्ट्रिंग कॉपी करने के लिए । ऐसा नहीं है कि अगर आप एक और स्ट्रिंग असाइन करते हैं तो इसे आंतरिक रूप से एक नया बफर फिर से आवंटित करना होगा। शायद, एक नई स्ट्रिंग को असाइन करना सिर्फ कुछ बाइट्स कॉपी करता है। std :: स्ट्रिंग में बाइट्स आरक्षित करने की क्षमता है। तो आप "name.reserve (25)" पर कॉल कर सकते हैं; " आपके कन्स्ट्रक्टर में और यदि आप कुछ छोटा असाइन करते हैं तो यह फिर से आवंटित नहीं होगा। (मैंने परीक्षण किए हैं, और ऐसा लगता है कि यदि आप किसी अन्य std :: स्ट्रिंग से असाइन करते हैं तो जीसीसी हमेशा पुन: आवंटित होता है, लेकिन यदि आप सी-स्ट्रिंग से असाइन नहीं करते हैं। They say उनके पास एक कॉपी-ऑन-राइट स्ट्रिंग है, जो उस व्यवहार को समझाएगी)।

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

संपादित:

यह पता चला है, वहाँ वर्गों है कि एक कुशल swap कार्यान्वयन (निरंतर समय) के लिए यह करने के लिए एक बेहतर तरीका है:

void setName(std::string parameter_name) { 
    name.swap(parameter_name); 
} 

कारण यह है कि यह वह जगह है बेहतर, यह है कि अब कॉलर जानता है कि तर्क की प्रतिलिपि बनाई जा रही है। वापसी मान अनुकूलन और समान अनुकूलन अब संकलक द्वारा आसानी से लागू किया जा सकता है। इस मामले पर विचार करें, उदाहरण के

obj.setName("Mr. " + things.getName()); 

आप था setName एक संदर्भ लेते हैं, तो अस्थायी तर्क में बनाया है कि संदर्भ के लिए बाध्य किया जाएगा, और setName के भीतर इसे कॉपी किया है, और के लिए यह रिटर्न के बाद, अस्थायी नष्ट हो जाएगा - जो वैसे भी एक फेंकने वाला उत्पाद था। यह केवल उपमहाद्वीप है, क्योंकि इसकी प्रतिलिपि के बजाय अस्थायी स्वयं का उपयोग किया जा सकता था। पैरामीटर को संदर्भ न देने से कॉलर को यह पता चल जाएगा कि तर्क की प्रतिलिपि बनाई जा रही है, और ऑप्टिमाइज़र की नौकरी को और अधिक आसान बनाते हैं - क्योंकि यह देखने के लिए कॉल को इनलाइन करना होगा कि तर्क की प्रतिलिपि बनाई गई है।

आगे स्पष्टीकरण के लिए, उत्कृष्ट लेख BoostCon09/Rvalue-References

1

आप स्ट्रिंग को अनलेश मॉन्कीफ़िश स्थिर बना सकते हैं लेकिन मुझे नहीं लगता कि वास्तव में कुछ भी मदद करता है (और यह कैसे लागू किया जाता है इसके आधार पर काफी खराब हो सकता है)।

मैंने उच्च स्तरीय भाषाओं (जैसे सी #, जावा) से "नीचे" स्थानांतरित कर दिया है और हाल ही में इस मुद्दे को मारा है। मुझे लगता है कि अक्सर स्ट्रिंग की प्रतिलिपि बनाना एकमात्र विकल्प है।

1

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

जैसा कि एंड्रयू फ्लानगन ने उल्लेख किया है, आप स्थानीय स्थैतिक चर या निरंतर उपयोग करके स्ट्रिंग प्रति से बच सकते हैं।

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

3

बस शब्दावली को स्पष्ट करने के लिए, आपने ढेर से बंदरफिश (नया उपयोग करके) और स्टैक पर स्थानीय नाम बनाया है।

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

क्या आप स्पष्ट कर सकते हैं कि आप स्ट्रिंग को कॉपी क्यों नहीं करना चाहते हैं?

संपादित

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

... उम्मीद है कि कुछ अर्थों :)

+0

सुधार के लिए धन्यवाद - पोस्ट को अपडेट करना। – Runcible

+0

जहां तक ​​मैं स्ट्रिंग की प्रतिलिपि बनाना नहीं चाहता हूं - यह ठीक है जैसा आपने कहा है: मैं इसे बहुत कुछ करने जा रहा हूं। दुनिया में पर्याप्त बंदरफिश नहीं है। – Runcible

5

आप निम्न विधि घोषणा का उपयोग करते हैं बनाया:

void setName(const std::string & parameter_name); 

तो आप भी सदस्य घोषणा का प्रयोग करेंगे:

std::string name; 

और setName शरीर में असाइनमेंट:

name = parameter_name; 

आप सदस्य को संदर्भ के रूप में घोषित नहीं कर सकते क्योंकि आप ऑब्जेक्ट कन्स्ट्रक्टर में एक संदर्भ सदस्य प्रारंभ करना चाहते हैं (जिसका अर्थ है कि आप इसे setName में सेट नहीं कर पाएंगे)।

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

+0

हां, संदर्भ के बारे में अच्छा बिंदु। यह मेरी गलती है। इसे केवल कन्स्ट्रक्टर में ही सेट किया जाना चाहिए। – Runcible

2

पढ़ यह ठीक समस्या यह है कि संदर्भ गिनती हल करने के लिए मतलब है। स्ट्रिंग ऑब्जेक्ट को इस तरह से संदर्भित करने के लिए आप Boost shared_ptr <> बूस्ट का उपयोग कर सकते हैं जैसे कि यह कम से कम तब तक प्रत्येक पॉइंटर के रूप में रहता है।

व्यक्तिगत रूप से मैं इसे कभी भरोसा नहीं करता, हालांकि, मेरी सभी वस्तुओं के आवंटन और जीवनकाल के बारे में स्पष्ट होना पसंद करते हैं। litb का समाधान बेहतर है।

+0

क्या यह कहना उचित है कि litb का समाधान एक प्रतिलिपि होने का कारण बनता है, लेकिन shared_ptrs का उपयोग प्रतिलिपि से बच जाएगा? – Runcible

+0

shared_ptr आपको unleashMonkeyFish() के अंदर स्थानीय नाम "नया" करने के लिए बाध्य करता है, क्योंकि यह ढेर पर रहना चाहिए और स्टैक नहीं यदि आप इसे फ़ंक्शन स्कोप से बाहर निकलना चाहते हैं। व्यावहारिक रूप से वे शायद दोनों ही प्रतियों की संकलन करते हैं क्योंकि नए std :: string() कन्स्ट्रक्टर को भी एक प्रतिलिपि बनाना चाहिए! – Crashworks

+0

shared_ptr को स्ट्रिंग को नया करने की आवश्यकता होगी। मैं तर्क देता हूं कि केवल 4 बाइट चीजों को ढेर से आवंटित करने के बजाय वर्णों के 5 बाइटों की प्रतिलिपि बनाने से भी बदतर है (और फिर पुरानी स्ट्रिंग को हटाएं जो आपके डेटा-सदस्य-साझा_प्टर पहले इंगित कर रहे थे) :) –

2

जब संकलक देखता है ...

std::string localname = "Wanda"; 

... यह (अनुकूलन जादू को छोड़कर) होगा फेंकना 0x57 0x61 0x6E 0x64 0x61 0x00 [अशक्त टर्मिनेटर साथ वांडा] और स्थिर में कहीं यह स्टोर आपके कोड का खंड फिर यह std :: string (const char *) का आह्वान करेगा और उसे उस पते को पास करेगा।चूंकि कन्स्ट्रक्टर के लेखक को आपूर्ति किए गए कॉन्स char * के जीवनकाल को जानने का कोई तरीका नहीं है, इसलिए उसे एक प्रतिलिपि बनाना होगा। MonkeyFish :: setName (const std :: string &) में, कंपाइलर std :: string :: ऑपरेटर = (const std :: string &) देखेंगे, और यदि आपकी std :: स्ट्रिंग को कॉपी-ऑन- अर्थशास्त्र लिखें, संकलक संदर्भ गणना बढ़ाने के लिए कोड उत्सर्जित करेगा लेकिन कोई प्रतिलिपि नहीं बनायेगा।

इस प्रकार आप एक प्रतिलिपि के लिए भुगतान करेंगे। क्या आपको एक भी चाहिए? क्या आप संकलन समय पर जानते हैं कि बंदरफिश के नाम क्या होंगे? क्या बंदरफिश कभी भी ऐसे नामों को बदलता है जो संकलन समय पर ज्ञात नहीं हैं? यदि बंदरफिश के सभी संभावित नाम संकलित समय पर ज्ञात हैं, तो आप स्ट्रिंग अक्षर की स्थिर तालिका का उपयोग करके और बंदरफिश के डेटा सदस्य को कॉन्स्ट char * के रूप में कार्यान्वित करके सभी प्रतिलिपि से बच सकते हैं।

+0

फीडबैक tlholaday के लिए धन्यवाद। मैंने कुछ स्पष्टीकरण टिप्पणियां जोड़ दी हैं - लेकिन विशेष रूप से आपको जवाब देने के लिए: मुझे नहीं पता कि कितने बंदरफिश होंगे, और मैं संकलन समय पर उनके नाम नहीं जानता। – Runcible

2

अंगूठे के एक साधारण नियम के रूप में एक कक्षा के भीतर एक प्रतिलिपि के रूप में अपना डेटा स्टोर करते हैं, और (कॉन्स्ट) संदर्भ द्वारा डेटा पास और वापस करते हैं, जहां भी संभव हो संदर्भ संदर्भ काउंटर का उपयोग करें।

मैं स्ट्रिंग डेटा के कुछ 1000s बाइट्स की प्रतिलिपि बनाने के बारे में इतना चिंतित नहीं हूं, जब तक कि प्रोफाइलर कहता है कि यह एक महत्वपूर्ण लागत है। ओटीओएच मुझे परवाह है कि डेटा संरचनाओं में से कई 10 एमबी डेटा हैं जो कॉपी नहीं होते हैं।

2

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

class MonkeyFish { 
public: 
    void setName(const std::string & parameter_name) { name = parameter_name; } 

private: 
    std::string name; 
}; 

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

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

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