Std पास कैसे करें :: unique_ptr आसपास?


83

मैं C ++ 11 का उपयोग करते हुए अपना पहला प्रयास कर रहा हूं unique_ptr; मैं खान की एक परियोजना के अंदर एक पॉलीमॉर्फिक कच्चे पॉइंटर की जगह ले रहा हूं, जो एक वर्ग के स्वामित्व में है, लेकिन लगभग अक्सर गुजरता है।

मेरे पास फ़ंक्शंस होते थे:

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

लेकिन मुझे जल्द ही एहसास हुआ कि मैं इसमें नहीं जा पाऊंगा:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

क्योंकि कॉल करने वाले को फ़ंक्शन के लिए सूचक स्वामित्व को संभालना होगा, जो मैं नहीं चाहता। तो, मेरी समस्या का सबसे अच्छा समाधान क्या है?

हालांकि, मैं इस तरह से पॉइंटर को पास कर रहा हूं:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

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

जवाबों:


97

यदि आप चाहते हैं कि फ़ंक्शन पॉंट्टी का उपयोग करें, तो इसके लिए एक संदर्भ पास करें। केवल कुछ प्रकार के स्मार्ट पॉइंटर के साथ काम करने के लिए फ़ंक्शन को टाई करने का कोई कारण नहीं है:

bool func(BaseClass& base, int other_arg);

और कॉल साइट के उपयोग पर operator*:

func(*some_unique_ptr, 42);

वैकल्पिक रूप से, यदि baseतर्क को शून्य होने दिया जाता है, तो हस्ताक्षर को यथावत रखें और get()सदस्य फ़ंक्शन का उपयोग करें :

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

4
यह अच्छा है, लेकिन सांचा आपको std::unique_ptrएक std::vector<std::unique_ptr>तर्क के लिए छुटकारा दिलाता है?
सिरो सेंटिल्ली 郝海东 i i i 16 '10

31

उपयोग करने का लाभ std::unique_ptr<T>(एक तरफ से कॉल करने के लिए deleteया delete[]स्पष्ट रूप से याद नहीं रखने के लिए ) यह है कि यह गारंटी देता है कि एक सूचक nullptrया तो (आधार) ऑब्जेक्ट के एक वैध उदाहरण को इंगित करता है। के बाद मैं आपके प्रश्न का उत्तर मैं यह करने के लिए वापस आ जाएगा, लेकिन पहले संदेश है डीओ स्मार्ट संकेत का उपयोग गतिशील रूप से आवंटित वस्तुओं के जीवन का प्रबंधन करने के।

अब, आपकी समस्या वास्तव में अपने पुराने कोड के साथ इसका उपयोग करना है

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

bool func(BaseClass& ref, int other_arg) { ... }

फिर कॉल करने वाला, जिसके पास std::shared_ptr<BaseClass> ptrया तो nullptrमामला होगा या वह bool func(...)परिणाम की गणना करने के लिए कहेगा :

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

इसका मतलब है कि किसी भी कॉलर को यह वादा करना होगा कि संदर्भ वैध है और यह फ़ंक्शन निकाय के निष्पादन के दौरान मान्य रहेगा।


यही कारण है कि मैं दृढ़ता से मानता हूं कि आपको कच्चे पॉइंटर्स या स्मार्ट पॉइंटर्स के संदर्भों को पारित नहीं करना चाहिए ।

एक कच्चा सूचक केवल एक स्मृति पता है। एक (कम से कम) 4 अर्थ हो सकते हैं:

  1. मेमोरी के एक ब्लॉक का पता जहां आपकी इच्छित वस्तु स्थित है। ( अच्छा )
  2. पता 0x0 जो कि आप निश्चित हो सकते हैं, निंदनीय नहीं है और इसमें "कुछ भी नहीं" या "अन्य वस्तु" का शब्दार्थ हो सकता है। ( बुरा )
  3. स्मृति के एक ब्लॉक का पता जो आपकी प्रक्रिया के पता योग्य स्थान के बाहर है (यह आपके कार्यक्रम को दुर्घटनाग्रस्त होने का कारण होगा)। ( बदसूरत )
  4. मेमोरी के एक ब्लॉक का पता जिसे डिरेल किया जा सकता है लेकिन जिसमें वह नहीं है जिसकी आप अपेक्षा करते हैं। हो सकता है कि पॉइंटर को गलती से संशोधित किया गया था और अब यह एक और लिखने योग्य पते (आपकी प्रक्रिया के भीतर एक पूरी तरह से अन्य चर) की ओर इशारा करता है। इस मेमोरी लोकेशन पर लिखने से, निष्पादन के दौरान, कई बार बहुत मज़ा आएगा, क्योंकि जब तक आपको वहां लिखने की अनुमति नहीं होगी, तब तक ओएस शिकायत नहीं करेगा। ( Zoinks! )

सही ढंग से स्मार्ट पॉइंटर्स का उपयोग करने से डरावने मामलों की संख्या 3 और 4 कम हो जाती है, जो आमतौर पर संकलन के समय में पता लगाने योग्य नहीं होते हैं और जिन्हें आप आमतौर पर रनटाइम के दौरान अनुभव करते हैं जब आपका प्रोग्राम क्रैश हो जाता है या अप्रत्याशित चीजें करता है।

तर्क के रूप में स्मार्ट पॉइंटर्स को पास करने के दो नुकसान हैं: आप बिना कॉपी किए (जो ओवरहेड जोड़ता है और जिसके लिए संभव नहीं है ) बिना इंगित किए गए ऑब्जेक्ट की const-ness को नहीं बदल सकते हैं , और आप अभी भी दूसरे ( ) अर्थ के साथ छोड़ दिए गए हैं ।shared_ptrunique_ptrnullptr

मैंने एक डिजाइन के नजरिए से दूसरे मामले को ( खराब ) के रूप में चिह्नित किया । यह जिम्मेदारी के बारे में अधिक सूक्ष्म तर्क है।

कल्पना करें कि जब कोई फ़ंक्शन nullptrअपने पैरामीटर के रूप में प्राप्त करता है तो इसका क्या अर्थ है । यह पहले तय करना है कि इसके साथ क्या करना है: लापता वस्तु के स्थान पर "जादुई" मूल्य का उपयोग करें? व्यवहार को पूरी तरह से बदल दें और कुछ और गणना करें (जिसे ऑब्जेक्ट की आवश्यकता नहीं है)? घबराहट और एक अपवाद फेंक? इसके अलावा, तब क्या होता है जब फ़ंक्शन कच्चे सूचक द्वारा 2 या 3 या उससे अधिक तर्क लेता है? इसमें से प्रत्येक को जांचना होगा और उसके अनुसार अपने व्यवहार को अनुकूलित करना होगा। यह बिना किसी वास्तविक कारण के इनपुट सत्यापन के शीर्ष पर एक नया स्तर जोड़ता है।

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


25

मैं मार्टिनो से सहमत हूं, लेकिन मुझे लगता है कि पास-पास-संदर्भ के स्वामित्व शब्दार्थ को इंगित करना महत्वपूर्ण है। मुझे लगता है कि सही समाधान यहां एक साधारण पास-बाय-संदर्भ का उपयोग करना है:

bool func(BaseClass& base, int other_arg);

C ++ में पास-बाय-रेफरेंस का आम तौर पर स्वीकृत अर्थ ऐसा है जैसे कि फ़ंक्शन का कॉलर फ़ंक्शन को बताता है "यहां, आप इस ऑब्जेक्ट को उधार ले सकते हैं, इसका उपयोग कर सकते हैं और इसे संशोधित कर सकते हैं (यदि कब्ज नहीं है), लेकिन केवल इसके लिए फ़ंक्शन बॉडी की अवधि। " यह किसी भी तरह से, मालिकाना नियमों के विरोध में नहीं है unique_ptrक्योंकि वस्तु केवल थोड़े समय के लिए उधार ली जा रही है, कोई वास्तविक स्वामित्व हस्तांतरण नहीं हो रहा है (यदि आप किसी को अपनी कार उधार देते हैं, तो क्या आप शीर्षक पर हस्ताक्षर करते हैं उस पर?)

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


0

व्यक्तिगत रूप से, मैं एक पॉइंटर / स्मार्ट पॉइंटर से एक संदर्भ खींचने से बचता हूं। क्योंकि पॉइंटर होने पर क्या होता है nullptr? यदि आप इस पर हस्ताक्षर बदलते हैं:

bool func(BaseClass& base, int other_arg);

आपको अपने कोड को नल पॉइंटर डेरेफेर से बचाना होगा:

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

यदि कक्षा सूचक का एकमात्र स्वामी है, तो मार्टिनो के विकल्प का दूसरा विकल्प अधिक उचित लगता है:

func(the_unique_ptr.get(), 10);

वैकल्पिक रूप से, आप उपयोग कर सकते हैं std::shared_ptr। हालाँकि, यदि एक एकल इकाई के लिए जिम्मेदार है delete, तो std::shared_ptrओवरहेड भुगतान नहीं करता है।


क्या आप जानते हैं कि, अधिकांश महासागरों में, std::unique_ptrशून्य ओवरहेड है, है ना?
लवेल्ला

1
हां, मुझे इसकी जानकारी है। इसलिए मैंने उसे std::shared_ptrओवरहेड के बारे में बताया ।
गुइलहर्मे फरेरा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.