आशय व्यक्त करने के संदर्भ में, यहां अधिकांश उत्तर फ़ंक्शन हस्ताक्षर में कच्चे सूचक होने में निहित अस्पष्टता को संबोधित करने में विफल होते हैं। समस्याएं निम्नलिखित हैं:
कॉल करने वाले को यह नहीं पता होता है कि सूचक एकल ऑब्जेक्ट्स की ओर इशारा करता है, या वस्तुओं के "सरणी" की शुरुआत के लिए।
कॉल करने वाले को यह नहीं पता होता है कि सूचक उस मेमोरी को "मालिक" करता है या नहीं। IE, फ़ंक्शन स्मृति को मुक्त करना चाहिए या नहीं। ( foo(new int)
- क्या यह मेमोरी लीक है?)।
कॉल करने वाले को यह नहीं पता है कि nullptr
फ़ंक्शन में सुरक्षित रूप से पारित किया जा सकता है या नहीं ।
इन सभी समस्याओं को संदर्भ द्वारा हल किया जाता है:
संदर्भ हमेशा एक ही वस्तु को संदर्भित करते हैं।
संदर्भ कभी भी उनके द्वारा संदर्भित स्मृति के मालिक नहीं होते हैं, वे केवल स्मृति में एक दृश्य हैं।
संदर्भ शून्य नहीं हो सकते।
यह संदर्भ को सामान्य उपयोग के लिए बेहतर उम्मीदवार बनाता है। हालांकि, संदर्भ सही नहीं हैं - विचार करने के लिए कुछ प्रमुख समस्याएं हैं।
- कोई स्पष्ट अप्रत्यक्ष नहीं। यह कच्चे पॉइंटर के साथ कोई समस्या नहीं है, क्योंकि हमें
&
ऑपरेटर को यह दिखाने के लिए उपयोग करना होगा कि हम वास्तव में एक पॉइंटर पास कर रहे हैं। उदाहरण के लिए, int a = 5; foo(a);
यह यहाँ बिल्कुल स्पष्ट नहीं है कि संदर्भ द्वारा पारित किया जा रहा है और संशोधित किया जा सकता है।
- Nullability। बिंदुओं की यह कमजोरी भी एक ताकत हो सकती है, जब हम वास्तव में चाहते हैं कि हमारे संदर्भ अशक्त हों। के रूप में देखकर
std::optional<T&>
मान्य नहीं है (अच्छे कारणों के लिए) संकेत हमें उस nullability आप चाहते हैं देना है।
तो ऐसा लगता है कि जब हम स्पष्ट अप्रत्यक्ष के साथ एक अशक्त संदर्भ चाहते हैं, तो हमें एक T*
अधिकार के लिए पहुंचना चाहिए ? गलत!
कपोल-कल्पना
अशक्तता के लिए हमारी हताशा में, हम तक पहुँच सकते हैं T*
, और बस पहले से सूचीबद्ध सभी कमियों और अर्थ अस्पष्टताओं को अनदेखा कर सकते हैं । इसके बजाय, हमें सी ++ के लिए सबसे अच्छा काम करना चाहिए: एक अमूर्त। यदि हम बस एक वर्ग लिखते हैं जो एक पॉइंटर के चारों ओर घूमता है, तो हम अभिव्यंजकता प्राप्त करते हैं, साथ ही अशक्तता और स्पष्ट अप्रत्यक्षता भी।
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
यह सबसे सरल इंटरफ़ेस है जिसके साथ मैं आ सकता था, लेकिन यह प्रभावी ढंग से काम करता है। यह संदर्भ को इनिशियलाइज़ करने की अनुमति देता है, यह जाँचता है कि क्या कोई मान मौजूद है और मान को एक्सेस कर रहा है। हम इसे इस तरह उपयोग कर सकते हैं:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
हमने अपने लक्ष्य को पा लिया है! आइए अब कच्चे सूचक की तुलना में लाभ देखें।
- इंटरफ़ेस स्पष्ट रूप से दिखाता है कि संदर्भ केवल एक वस्तु को संदर्भित करना चाहिए।
- स्पष्ट रूप से यह उस मेमोरी का मालिक नहीं है जिसका वह उल्लेख करता है, क्योंकि इसमें कोई उपयोगकर्ता परिभाषित डिस्ट्रक्टर नहीं है और मेमोरी को हटाने की कोई विधि नहीं है।
- कॉलर जानता है
nullptr
कि पारित किया जा सकता है, क्योंकि फ़ंक्शन लेखक स्पष्ट रूप से एक के लिए पूछ रहा हैoptional_ref
हम इंटरफ़ेस को यहाँ से और अधिक जटिल बना सकते हैं, जैसे कि समानता ऑपरेटर, एक मोनैडिक get_or
और map
इंटरफ़ेस जोड़ना , एक ऐसा तरीका जो मूल्य प्राप्त करता है या एक अपवाद, constexpr
समर्थन को फेंकता है । जो आपके द्वारा किया जा सकता है।
अंत में, कच्चे बिंदुओं का उपयोग करने के बजाय, उन बिंदुओं का क्या कारण है जो आपके कोड में वास्तव में हैं, और या तो एक मानक पुस्तकालय अमूर्तता का लाभ उठाएं या अपना खुद का लिखें। इससे आपके कोड में काफी सुधार होगा।
new
एक सूचक और स्वामित्व के परिणामस्वरूप मुद्दों को बनाने के बारे में मत भूलना ।