c++ Std:: जोड़ी सदस्य चर का पर्दाफाश क्यों करता है?




stl encapsulation (6)

http://www.cplusplus.com/reference/utility/pair/ , हम जानते हैं कि std::pair में दो सदस्य चर हैं, first और second

एसटीएल डिजाइनरों ने getFirst() और getFirst() की पेशकश करने के बजाय, first और second दो सदस्य चर का पर्दाफाश करने का फैसला क्यों किया?


गेटर्स और सेटर आमतौर पर उपयोगी होते हैं यदि कोई सोचता है कि मूल्य प्राप्त करने या सेट करने के लिए अतिरिक्त तर्क (कुछ आंतरिक स्थिति बदलना) की आवश्यकता होती है। इसे विधि में आसानी से जोड़ा जा सकता है। इस मामले में std::pair का उपयोग केवल 2 डेटा मान प्रदान करने के लिए किया जाता है। न कुछ ज्यादा, न कुछ कम। और इस प्रकार, गेटर और सेटर की वर्बोजिटी जोड़ना व्यर्थ होगा।


यह तर्क दिया जा सकता है कि std::pair अपने सदस्यों तक पहुंचने के लिए एक्सेसर फ़ंक्शन रखने से बेहतर होगी! विशेष रूप से std::pair अपरिवर्तित मामलों के लिए एक फायदा हो सकता है। उदाहरण के लिए, जब कम से कम एक प्रकार का खाली, गैर-अंतिम वर्ग होता है, तो वस्तुएं छोटी हो सकती हैं (खाली आधार को आधार बनाया जा सकता है जिसे अपना स्वयं का पता प्राप्त करने की आवश्यकता नहीं होती है)।

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

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

मैं निर्णय का हिस्सा नहीं था और सी ++ मानक में कोई तर्क नहीं था लेकिन मुझे लगता है कि सदस्यों को सार्वजनिक डेटा सदस्यों को बनाने का एक जानबूझकर निर्णय था।


गेटर्स और सेटर उपयोगी होते हैं यदि कोई मानता है कि अब तक या भविष्य में उन विकल्पों में डिज़ाइन विकल्पों और परिवर्तनों से उपयोगकर्ताओं को अपनाने के लिए अमूर्तता की आवश्यकता है

"अब" के लिए सामान्य उदाहरण यह है कि सेटटर / गेटर के पास वैधता और / या मूल्य की गणना करने के लिए तर्क हो सकता है - उदाहरण के लिए, सीधे क्षेत्र को उजागर करने के बजाय, फ़ोन नंबर के लिए एक सेटटर का उपयोग करें, ताकि आप प्रारूप की जांच कर सकें; संग्रह के लिए गेटर का उपयोग करें ताकि गेटर कॉलर को सदस्य के मूल्य (संग्रह) का केवल-पढ़ने वाला दृश्य प्रदान कर सके।

"भविष्य में परिवर्तन" के लिए getY() हालांकि खराब ) उदाहरण Point - क्या आपको x और y या getY() और getY() पर्दाफाश करना चाहिए? सामान्य उत्तर गेटर्स / सेटर्स का उपयोग करना है क्योंकि भविष्य में कुछ समय में आप कार्टेसियन से ध्रुवीय तक आंतरिक प्रतिनिधित्व बदलना चाहते हैं और आप नहीं चाहते हैं कि आपके उपयोगकर्ता प्रभावित हों (या उन्हें उस डिजाइन निर्णय पर निर्भर रखें) ।

std::pair के मामले में - यह इरादा है कि यह वर्ग अब और हमेशा के लिए दो और बिल्कुल दो मान (मनमानी प्रकार के) का प्रतिनिधित्व करता है, और मांग पर अपने मूल्य प्रदान करता है। बस। और यही कारण है कि डिज़ाइन गेटर / सेटर के माध्यम से जाने के बजाय, सीधे सदस्य पहुंच का उपयोग करता है।


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

उदाहरण के लिए, कहें कि आपके पास एक कक्षा है जो नाव पर यात्रियों की संख्या का प्रतिनिधित्व करती है। आप यात्रियों की संख्या को पूर्णांक के रूप में संग्रहीत करते हैं। यदि आप उस संख्या को एक नंगे चर के रूप में बेनकाब करते हैं, तो बाहरी कार्यों को लिखना संभव होगा। इससे आपको ऐसे मामले में छोड़ दिया जा सकता है जहां वास्तव में 10 यात्री हैं, लेकिन किसी ने परिवर्तनीय (शायद गलती से) 50 को बदल दिया है। यह यात्रियों की संख्या पर एक गेटर के लिए एक मामला है (लेकिन एक सेटटर नहीं, जो वही पेश करेगा समस्या )।

गेटर्स और सेटर्स के लिए एक उदाहरण एक वर्ग होगा जो गणितीय वेक्टर का प्रतिनिधित्व करता है जिसमें आप वेक्टर के बारे में कुछ जानकारी कैश करना चाहते हैं। मान लें कि आप लंबाई को स्टोर करना चाहते हैं। इस मामले में, vec.x बदलना शायद लंबाई / परिमाण को बदल देगा। इसलिए, न केवल आपको गेटटर में एक्स को लपेटने की ज़रूरत है, आपको एक्स के लिए एक सेटटर प्रदान करना होगा, जो वेक्टर की कैश की लंबाई को अपडेट करना जानता है। (बेशक, अधिकांश वास्तविक गणित पुस्तकालय इन मानों को कैश नहीं करते हैं, और इस प्रकार वेरिएबल्स का पर्दाफाश करते हैं।)

तो सवाल यह है कि आपको स्वयं का उपयोग करने के संदर्भ में खुद से पूछना चाहिए: क्या इस वर्ग को कभी भी इस चर में परिवर्तनों को नियंत्रित करने या चेतावनी देने की आवश्यकता है?

Std :: pair जैसे कुछ में जवाब एक फ्लैट "नहीं" है। कक्षा में सदस्यों तक पहुंच नियंत्रित करने का कोई मामला नहीं है जिसका एकमात्र उद्देश्य उन सदस्यों को शामिल करना है। निश्चित रूप से यह जानने के लिए जोड़ी की आवश्यकता नहीं है कि उन चरों को छुआ है, क्योंकि उनके केवल दो सदस्य हैं, और इस प्रकार अद्यतन करने के लिए कोई राज्य नहीं है या तो बदलना चाहिए। जोड़ी वास्तव में इसमें शामिल है और इसका अर्थ अज्ञानी है, इसलिए इसमें जो भी शामिल है उसे ट्रैक करना प्रयास के लायक नहीं है।

कंपाइलर के आधार पर और यह कैसे कॉन्फ़िगर किया गया है, गेटर्स और सेटर्स ओवरहेड पेश कर सकते हैं। ज्यादातर मामलों में शायद यह महत्वपूर्ण नहीं है, लेकिन यदि आप उन्हें std::pair जैसी मौलिक चीज़ पर रखना चाहते हैं, तो यह एक गैर-मामूली चिंता होगी। इस प्रकार, उनके अतिरिक्त को उचित ठहराने की आवश्यकता होगी - जैसा कि मैंने अभी समझाया है, यह नहीं हो सकता है।


इसका कारण यह है कि डेटा संरचना पर कोई असली आविष्कार लागू नहीं किया जाना चाहिए, क्योंकि std::pair मॉडल दो तत्वों के लिए एक सामान्य उद्देश्य कंटेनर मॉडल करता है। दूसरे शब्दों में, टाइप की एक वस्तु std::pair<T, U> क्रमशः T और U प्रकार के किसी भी संभावित first और second तत्व के लिए वैध मानी जाती है। इसी प्रकार, इसके तत्वों के मूल्य में बाद में उत्परिवर्तन वास्तव में std::pair per se की वैधता को प्रभावित नहीं कर सकते हैं।

एलेक्स स्टेपानोव (एसटीएल के लेखक) स्पष्ट रूप से singleton कंटेनर (यानी, एक तत्व के कंटेनर) पर टिप्पणी करते समय, घटक के साथ कुशल प्रोग्रामिंग के दौरान इस सामान्य डिजाइन सिद्धांत को स्पष्ट रूप से प्रस्तुत करते हैं।

इस प्रकार, यद्यपि सिद्धांत स्वयं ही बहस का स्रोत हो सकता है, यह std::pair के आकार के पीछे कारण है।


मैं उन टिप्पणियों की संख्या से चिंतित था जो वस्तु-उन्मुख डिजाइन की कोई बुनियादी समझ नहीं दिखाते हैं (क्या यह साबित करता है कि सी ++ ओओ-भाषा नहीं है?)। हां std :: pair के डिज़ाइन में कुछ ऐतिहासिक लक्षण हैं, लेकिन इससे कोई खराब डिज़ाइन अच्छा नहीं होता है; न ही इस तथ्य को अस्वीकार करने के बहाने के रूप में इस्तेमाल किया जाना चाहिए। इससे पहले कि मैं इस पर रुकूं, मुझे टिप्पणियों में से कुछ सवालों का जवाब दें:

क्या आपको नहीं लगता कि int को एक सेटर और गेटर भी होना चाहिए

हां, एक डिजाइन बिंदु से हमें एक्सेसर्स का उपयोग करना चाहिए क्योंकि ऐसा करके हम कुछ भी नहीं खोते हैं बल्कि अतिरिक्त लचीलापन प्राप्त करते हैं। कुछ नए एल्गोरिदम कुंजी / मानों में अतिरिक्त बिट्स पैक करना चाहते हैं, और आप उन्हें एक्सेसर्स के बिना एन्कोड / डीकोड नहीं कर सकते हैं।

गेटटर में कोई तर्क नहीं होने पर गेटटर में कुछ लपेटें क्यों?

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

लोकप्रिय धारणा के विपरीत, ऐसी वस्तुएं जो कुछ भी नहीं करती हैं लेकिन गेटर्स और सेटर्स के साथ स्टोर सदस्य चर को "जिस तरह से चीजें की जानी चाहिए"

एक और जंगली अनुमान।

ठीक है, मैं यहाँ रुक जाऊंगा।

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

मैंने देखा कि वे std :: unordered_map के कार्यान्वयन के लिए ओपन हैशिंग (यानी बंद चेनिंग) के उपयोग को भी जरूरी करते हैं। यह डिज़ाइन बिंदु दृश्य से केवल अजीब नहीं है (आप प्रतिबंधित क्यों करना चाहते हैं कि चीजें कैसे कार्यान्वित की जाती हैं ???), लेकिन कार्यान्वयन के मामले में भी सुंदर गूंगा - जुड़ी हुई सूची का उपयोग करके जंजीर हैशटेबल्स शायद सभी श्रेणियों में सबसे धीमी है। वेब पर जाएं, हम आसानी से उस std को पा सकते हैं: unordered_map अक्सर हैशटेबल बेंचमार्क का डोरमेट होता है। यह जावा के हैश मैप से भी धीमा हो जाता है (मुझे नहीं पता कि वे इस मामले में कैसे पीछे हटने में कामयाब रहे, क्योंकि हैश मैप भी एक जंजीर हैशटेबल है)। एक पुराना बहाना यह है कि जब लोड_फैक्टर 1 तक पहुंचता है तो जंजीर हैशटेबल बेहतर प्रदर्शन करता है, जो पूरी तरह से अमान्य है क्योंकि 1) इस समस्या से निपटने के लिए खुले पते वाले परिवार में बहुत सारी तकनीकें हैं - कभी भी होप्सकॉटिंग या रॉबिन-हुड हैशिंग के बारे में सुना है, और उत्तरार्द्ध वास्तव में 30 अजीब वर्षों के लिए वहां रहा है; 2) एक जंजीर हैशटेबल प्रत्येक प्रविष्टि के लिए एक सूचक (64 बिट मशीनों पर एक अच्छा 8 बाइट) के ऊपरी हिस्से को जोड़ता है, इसलिए जब हम कहते हैं कि unordered_map दृष्टिकोण 1 का load_factor है, तो यह 100% स्मृति उपयोग नहीं है! हमें इसे समान स्मृति उपयोग के विकल्पों के साथ unordered_map के प्रदर्शन पर विचार करना चाहिए और तुलना करना चाहिए। और यह पता चला है कि Google Dense हैश मैप जैसे विकल्प std :: unordered_map से 3-4 गुना तेज है।

ये प्रासंगिक क्यों हैं? क्योंकि दिलचस्प बात यह है कि खुले हैंशिंग को अनिवार्य करने से std :: जोड़ी का डिज़ाइन कम खराब दिखता है, अब हमें वैकल्पिक स्टोरेज स्ट्रक्चर की लचीलापन की आवश्यकता नहीं है। इसके अलावा, std :: pair की उपस्थिति std :: unordered_map के ड्रॉप-इन प्रतिस्थापन को लिखने के लिए नए / बेहतर एल्गोरिदम को अपनाने के लिए लगभग असंभव बनाती है। कभी-कभी आप सोचते हैं कि उन्होंने जानबूझकर ऐसा किया है कि std :: जोड़ी का खराब डिज़ाइन और std :: unordered_map का पैदल यात्री कार्यान्वयन एक साथ रह सकता है। बेशक मैं मजाक कर रहा हूं, इसलिए जो भी उन्हें लिखता है, नाराज मत हो। वास्तव में जावा या पायथन का उपयोग करने वाले लोग (ठीक है, मैं पाइथन का एक खिंचाव स्वीकार करता हूं) उन्हें "सी ++ के रूप में तेज़" होने के बारे में अच्छा महसूस करने के लिए धन्यवाद देना चाहता हूं।







encapsulation