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

लेकिन C में यह एक बहुत ही कम मात्रा में कोड को reallocएक विकसित करने योग्य सरणी में ले जाता है और एक नई डेटा संरचना को लागू करते समय एक मुक्त सूची में अनुक्रमित दृष्टिकोण का उपयोग करके इसमें से कुछ मेमोरी को पूल करता है।
अब मेरे पास C ++ में लागू की गई एक ही चीज है और वहां मैंने केवल एक बार क्लास टेम्पलेट के रूप में इसे लागू किया है। लेकिन यह कोड की सैकड़ों लाइनों और कुछ बाहरी निर्भरताओं के साथ C ++ पक्ष पर एक बहुत अधिक जटिल कार्यान्वयन है, जो कोड की सैकड़ों लाइनों को भी फैलाता है। और मुख्य कारण यह बहुत अधिक जटिल है क्योंकि मुझे इसे उस विचार के खिलाफ कोड करना Tहोगा जो किसी भी संभव डेटा प्रकार का हो सकता है। यह किसी भी समय फेंक सकता है (इसे नष्ट करने के अलावा, जो मुझे मानक पुस्तकालय कंटेनरों के साथ स्पष्ट रूप से करना है), मुझे स्मृति को आवंटित करने के लिए उचित संरेखण के बारे में सोचना थाT (हालांकि सौभाग्य से यह C ++ 11 बाद में आसान हो गया है), यह गैर-तुच्छ रूप से रचनात्मक / विनाशकारी हो सकता है (प्लेसमेंट नए और मैनुअल डोर इनवोकेशन की आवश्यकता होती है), मुझे ऐसे तरीकों को जोड़ना होगा, जिसमें हर चीज की आवश्यकता नहीं होगी - इन चीजों की आवश्यकता होगी, और मुझे पुनरावृत्तियों को जोड़ना होगा, दोनों उत्परिवर्तनीय और केवल-पढ़ने (कास्ट) पुनरावृत्तियों, और इसी तरह और आगे।
बढ़ते एरर रॉकेट साइंस नहीं हैं
C ++ में लोग इसे साउंड बनाते हैं जैसे std::vectorकि रॉकेट वैज्ञानिक का काम है, मृत्यु के अनुकूल, लेकिन यह किसी विशिष्ट डेटा प्रकार के खिलाफ कोडित डायनामिक C सरणी से बेहतर प्रदर्शन नहीं करता है, जो सिर्फ reallocपुश बैक पर सरणी क्षमता बढ़ाने के लिए उपयोग करता है कोड की दर्जन लाइनें। अंतर यह है कि यह मानक के साथ सिर्फ एक विकसित यादृच्छिक-अभिगम अनुक्रम को पूरी तरह से अनुपालन करने के लिए एक बहुत ही जटिल कार्यान्वयन लेता है, असिंचित तत्वों पर ctors को आमंत्रित करने से बचें, अपवाद-सुरक्षित, दोनों कांस्ट और गैर-कॉन्स्टैंट रैंडम-एक्सेस पुनरावृत्तियों प्रदान करते हैं, प्रकार का उपयोग करते हैं कुछ अभिन्न प्रकारों के लिए रेंज सेटर्स से भराव को रोकने के लिए लक्षणTसंभावित रूप से PODs का उपयोग विभिन्न प्रकार के लक्षणों, इत्यादि इत्यादि के बारे में करें। उस बिंदु पर, जो आप करते हैं, वास्तव में, एक बहुत ही महत्वपूर्ण कार्यान्वयन की जरूरत है, ताकि एक गतिशील गतिशील सरणी बनाई जा सके, लेकिन केवल इसलिए कि यह हर संभव उपयोग के मामले को कभी कल्पना करने योग्य बनाने की कोशिश कर रहा है। प्लस साइड पर, आप पूरी तरह से बहुत सारा माइलेज प्राप्त कर सकते हैं, यदि आप पीयूडी और गैर-तुच्छ यूडीटी दोनों को स्टोर करने के लिए अतिरिक्त प्रयास करते हैं, तो किसी भी आज्ञाकारी डेटा संरचना पर जेनेरिक इट्रेटर-आधारित एल्गोरिदम का उपयोग करें। अपवाद-हैंडलिंग और RAII से लाभ, कम से कम कभी-कभी ओवरराइडstd::allocatorअपने स्वयं के कस्टम आवंटनकर्ता इत्यादि के साथ, यह निश्चित रूप से मानक पुस्तकालय में भुगतान करता है जब आप विचार करते हैं कि कितना लाभ हुआstd::vector पूरी दुनिया के उन लोगों पर पड़ा है जिन्होंने इसका उपयोग किया है, लेकिन यह पूरी दुनिया की जरूरतों को लक्षित करने के लिए डिज़ाइन किए गए मानक पुस्तकालय में लागू कुछ के लिए है।
बहुत ही विशिष्ट उपयोग मामलों को संभालने वाले सरल कार्यान्वयन
मेरी नि: शुल्क सूची के साथ बहुत विशिष्ट उपयोग के मामलों को संभालने के परिणामस्वरूप, इस नि: शुल्क सूची को सी साइड पर एक दर्जन बार लागू करने और परिणामस्वरूप कुछ तुच्छ कोड होने के बावजूद, मैंने शायद कम कोड लिखा है C को लागू करने के लिए कुल C कि मुझे C ++ में सिर्फ एक बार लागू करने की तुलना में एक दर्जन बार, और मुझे उस C C कार्यान्वयन को बनाए रखने की तुलना में उन दर्जन सी कार्यान्वयन को बनाए रखने में कम समय बिताना पड़ा। सी साइड होने का एक मुख्य कारण यह है कि मैं आमतौर पर C में POD के साथ काम कर रहा हूं जब भी मैं इस तकनीक का उपयोग करता हूं और मुझे आमतौर पर इससे अधिक कार्यों की आवश्यकता नहीं होती हैinsert औरeraseजिन विशिष्ट साइटों पर मैं इसे स्थानीय स्तर पर लागू करता हूं। मूल रूप से मैं कार्यक्षमता का सबसे छोटा सबसेट लागू कर सकता हूं, जो C ++ संस्करण प्रदान करता है, क्योंकि मैं जो कुछ करता हूं उसके बारे में बहुत अधिक धारणाएं बनाने के लिए स्वतंत्र हूं और जब मैं इसे बहुत विशिष्ट उपयोग के लिए लागू कर रहा हूं तो मुझे डिजाइन की आवश्यकता नहीं है। मामला।
अब C ++ संस्करण उपयोग करने के लिए बहुत अच्छा है और टाइप-सेफ है, लेकिन अभी भी इसे लागू करने के लिए एक प्रमुख PITA था और अपवाद-सुरक्षित और द्विदिश इटरेटर-कंप्लेंट को लागू करने के लिए, उदाहरण के लिए, एक सामान्य तरीके से आने वाले तरीकों में, पुन: लागू करने में शायद लागत आती है। इससे अधिक समय वास्तव में इस मामले में बचाता है। और इसे सामान्यीकृत तरीके से लागू करने की बहुत सारी लागत न केवल अपफ्रंट में बर्बाद होती है, बल्कि बार-बार बढ़ी हुई बिल्ड टाइम जैसी चीजों के रूप में बार-बार भुगतान की जाती है।
C ++ पर हमला नहीं!
लेकिन यह सी ++ पर हमला नहीं है क्योंकि मैं सी ++ से प्यार करता हूं, लेकिन जब डेटा संरचनाओं की बात आती है, तो मैं मुख्य रूप से गैर-तुच्छ डेटा संरचनाओं के लिए मुख्य रूप से सी ++ का पक्ष लेता हूं, जिसे लागू करने के लिए मैं बहुत अतिरिक्त समय बिताना चाहता हूं। एक बहुत ही सामान्यीकृत तरीका है, सभी संभावित प्रकारों के खिलाफ अपवाद-सुरक्षित करें T, मानक-अनुपालन और चलने योग्य, आदि बनाएं, जहां उस प्रकार की अग्रिम लागत वास्तव में एक टन के लाभ के रूप में भुगतान करती है।
फिर भी यह एक बहुत ही अलग डिजाइन मानसिकता को बढ़ावा देता है। सी ++ में अगर मैं टक्कर का पता लगाने के लिए एक ओक्ट्री बनाना चाहता हूं, तो मुझे इसे एनटीएच डिग्री पर सामान्य करने की इच्छा है। मैं सिर्फ इसे ट्राइएंगल ट्रायंगल मेश स्टोर करना नहीं चाहता। मुझे इसे केवल एक डेटा प्रकार तक सीमित क्यों करना चाहिए जब मैं अपनी उंगलियों पर एक सुपर शक्तिशाली कोड जनरेशन मैकेनिज्म काम कर सकता हूं जो रनटाइम के दौरान सभी अमूर्त दंड को समाप्त करता है? मैं चाहता हूं कि यह प्रक्रियात्मक क्षेत्रों, क्यूब्स, स्वरों, एनयूआरबी सतहों, बिंदु बादलों, आदि आदि को स्टोर करे और इसे हर चीज के लिए अच्छा बनाने की कोशिश करे, क्योंकि यह इस तरह से डिजाइन करना चाहता है जब आपकी उंगलियों पर टेम्पलेट हों। मैं इसे टकराव का पता लगाने के लिए सीमित नहीं करना चाहूंगा - कैसे पुनरावृत्ति, उठा, आदि के बारे में? C ++ इसे शुरू में "सॉर्टा आसान" बनाता है एनटीटी डिग्री के लिए एक डेटा संरचना को सामान्य करने के लिए। और इसी तरह मैंने C ++ में इस तरह के स्थानिक इंडेक्स को डिजाइन किया। मैंने उन्हें पूरी दुनिया की भूख की जरूरतों को संभालने के लिए डिजाइन करने की कोशिश की, और बदले में मुझे जो मिला वह आम तौर पर "सभी ट्रेडों का जैक" था जो कि सभी संभावित उपयोग के मामलों के खिलाफ इसे संतुलित करने के लिए बेहद जटिल कोड के साथ कल्पनाशील था।
हालांकि, पर्याप्त रूप से, मैंने पिछले वर्षों में सी में लागू किए गए स्थानिक अनुक्रमों से अधिक पुन: उपयोग किया है, और सी ++ की कोई गलती नहीं है, लेकिन केवल भाषा मुझे क्या करने के लिए मजबूर करती है। जब मैं सी में एक ऑक्ट्री की तरह कुछ कोड करता हूं, तो मुझे बस इसे अंकों के साथ काम करने और बस उसी के साथ खुश रहने की प्रवृत्ति है, क्योंकि भाषा इसे एनटीटी डिग्री तक सामान्य करने की कोशिश करना भी मुश्किल बनाती है। लेकिन उन प्रवृत्तियों के कारण, मैंने उन वर्षों में चीजों को डिजाइन करने का प्रयास किया है जो वास्तव में अधिक कुशल और विश्वसनीय हैं और हाथ में कुछ कार्यों के लिए वास्तव में अच्छी तरह से अनुकूल हैं, क्योंकि वे एनटी डिग्री के लिए सामान्य होने के साथ परेशान नहीं करते हैं। वे सभी ट्रेडों के जैक के बजाय एक विशेष श्रेणी में इक्के बन जाते हैं। फिर से C ++ की कोई गलती नहीं है, लेकिन बस जब मैं सी के विपरीत इसका उपयोग कर रहा हूं तो मानव प्रवृत्ति मेरे पास है।
लेकिन वैसे भी, मैं दोनों भाषाओं से प्यार करता हूं, लेकिन अलग-अलग प्रवृत्तियां हैं। सीआई में पर्याप्त सामान्यीकरण न करने की प्रवृत्ति होती है। C ++ में मेरे पास बहुत अधिक सामान्य करने की प्रवृत्ति है। दोनों का उपयोग करने से मुझे खुद को संतुलित करने में मदद मिली।
क्या सामान्य कार्यान्वयन एक आदर्श हैं, या क्या आप प्रत्येक उपयोग के मामले के लिए अलग-अलग कार्यान्वयन लिखते हैं?
एकल या एक सरणी से नोड्स का उपयोग करके 32-बिट अनुक्रमित सूचियों जैसी तुच्छ चीजों के लिए std::vector, जो वास्तविक रूप से स्वयं ( सी ++ में समतुल्य समतुल्य ) का उपयोग करता है या, कहो, एक ऑक्ट्री जो केवल अंकों को संग्रहीत करती है और कुछ भी नहीं करने का लक्ष्य रखती है, मैं डॉन ' किसी भी प्रकार के डेटा को संग्रहीत करने के बिंदु पर सामान्यीकरण करने की जहमत नहीं उठानी चाहिए। मैं इन्हें एक विशिष्ट डेटा प्रकार को स्टोर करने के लिए कार्यान्वित करता हूं (हालांकि यह सार हो सकता है और कुछ मामलों में फ़ंक्शन पॉइंटर्स का उपयोग कर सकता है, लेकिन स्थिर बहुरूपता के साथ बतख टाइपिंग की तुलना में कम से कम अधिक विशिष्ट है)।
और मैं उन मामलों में थोड़ी सी अतिरेक से पूरी तरह से खुश हूं, बशर्ते कि मैं इसे पूरी तरह से परखूं। यदि मैं इकाई परीक्षण नहीं करता हूं, तो अतिरेक अधिक असहज महसूस करने लगता है, क्योंकि आपके पास अनावश्यक कोड हो सकते हैं जो गलतियों की नकल कर सकते हैं, उदाहरण के लिए, भले ही आप जिस प्रकार का कोड लिख रहे हैं, उसे कभी भी डिजाइन में बदलाव की आवश्यकता नहीं है, यह अभी भी परिवर्तनों की आवश्यकता हो सकती है क्योंकि यह टूट गया है। मैं एक कारण के रूप में सी कोड के लिए अधिक गहन इकाई परीक्षण लिखना चाहता हूं।
Nontrivial चीजों के लिए, यह आमतौर पर तब होता है जब मैं C ++ के लिए पहुंचता हूं, लेकिन अगर मैं इसे C में लागू करने वाला था, तो मैं सिर्फ void*पॉइंटर्स का उपयोग करने पर विचार करूंगा, शायद यह जानने के लिए कि कोई मेमोरी कितने एलिमेंट को एलिमेंट करने के लिए एक टाइप साइज स्वीकार करती है, और संभवतः copy/destroyफंक्शन पॉइंटर्स यदि यह तुच्छ रूप से रचनात्मक / विनाशकारी नहीं है, तो डेटा की प्रतिलिपि बनाएँ और नष्ट करें। सबसे अधिक बार मैं परेशान नहीं करता हूं और सबसे जटिल डेटा संरचनाओं और एल्गोरिदम को बनाने के लिए बहुत सी का उपयोग नहीं करता हूं।
यदि आप किसी विशेष डेटा प्रकार के साथ अक्सर एक डेटा संरचना का उपयोग करते हैं, तो आप एक प्रकार का सुरक्षित संस्करण भी लपेट सकते हैं जो सिर्फ बिट्स और बाइट्स और फ़ंक्शन पॉइंटर्स के साथ काम करता है और void*, उदाहरण के लिए, सी रैपर के माध्यम से टाइप सुरक्षा का पुन: उपयोग करने के लिए।
मैं उदाहरण के लिए हैश मैप के लिए एक सामान्य कार्यान्वयन लिखने की कोशिश कर सकता हूं, लेकिन मुझे हमेशा गड़बड़ होने का अंतिम परिणाम मिल रहा है। मैं इस विशिष्ट उपयोग के मामले के लिए एक विशेष कार्यान्वयन भी लिख सकता हूं, कोड को स्पष्ट और पढ़ने और डिबग करने के लिए आसान रखें। बाद के कुछ कोड दोहराव के लिए नेतृत्व करेंगे।
हैश टेबल एक प्रकार की iffy हैं क्योंकि इसे लागू करना या वास्तव में जटिल हो सकता है यह इस बात पर निर्भर करता है कि आपकी ज़रूरतें हैश, रिहैसेस के संबंध में कितनी जटिल हैं, यदि आपको स्वचालित रूप से टेबल को अपने अनुमानित रूप से विकसित करने की आवश्यकता है या तालिका आकार का अनुमान लगा सकते हैं। एडवांस, चाहे आप ओपन एड्रेसिंग या अलग चैनिंग आदि का उपयोग करें, लेकिन एक बात का ध्यान रखें कि यदि आप किसी विशिष्ट साइट की जरूरतों के लिए हैश टेबल को पूरी तरह से सिल लेते हैं, तो यह अक्सर कार्यान्वयन में इतना जटिल नहीं होगा और अक्सर जीता जाता है। जब यह उन जरूरतों के लिए ठीक सिलवाया जाता है तो बेमानी न हों। कम से कम यही बहाना तो मैं खुद देता हूं अगर मैं स्थानीय स्तर पर कुछ लागू करता हूं। यदि नहीं, तो आप void*चीजों को कॉपी / नष्ट करने और इसे सामान्य बनाने के लिए ऊपर वर्णित विधि का उपयोग कर सकते हैं ।
बहुत सामान्य डेटा संरचना को हरा देने के लिए अक्सर यह बहुत अधिक प्रयास या अधिक कोड नहीं लेता है यदि आपका विकल्प आपके सटीक उपयोग के मामले में बेहद संकीर्ण है। एक उदाहरण के रूप में, mallocप्रत्येक नोड के लिए (प्रत्येक नोड के लिए मेमोरी का एक गुच्छा पूलिंग के विपरीत) एक बार और सभी कोड के साथ उपयोग करने के प्रदर्शन को हरा देने के लिए यह बिल्कुल तुच्छ है, कोड के साथ आपको कभी भी, बहुत सटीक उपयोग के मामले में फिर से आना नहीं पड़ता है। यहां तक कि नए कार्यान्वयन के रूप में mallocसामने आते हैं। यदि आप इसकी व्यापकता का मिलान करना चाहते हैं, तो इसे हराने और इसे कम करने के लिए आपको जीवन भर का समय लग सकता है, जिसे आपको अपने जीवन का एक बड़ा हिस्सा समर्पित करना होगा।
एक अन्य उदाहरण के रूप में, मैंने अक्सर समाधानों को लागू करना बहुत आसान पाया है जो कि पिक्सर या ड्रीमवर्क्स द्वारा प्रस्तुत वीएफएक्स समाधानों की तुलना में 10 गुना तेज या अधिक हैं। मैं इसे अपनी नींद में कर सकता हूं। लेकिन ऐसा इसलिए नहीं है क्योंकि मेरा क्रियान्वयन श्रेष्ठ है - इससे बहुत दूर। वे ज्यादातर लोगों के लिए नीच हैं। वे केवल मेरे बहुत, बहुत विशिष्ट उपयोग के मामलों के लिए बेहतर हैं। मेरे संस्करण बहुत दूर हैं, आमतौर पर पिक्सर या ड्रीमवर्क की तुलना में कम लागू होते हैं। यह एक हास्यास्पद अनुचित तुलना है क्योंकि उनके समाधान मेरे गूंगे-सरल समाधानों की तुलना में बिल्कुल शानदार हैं, लेकिन यह इस तरह का है। तुलना निष्पक्ष होने की जरूरत नहीं है। यदि आप सभी की जरूरत है कुछ बहुत ही विशिष्ट चीजें हैं, तो आप डेटा संरचना की जरूरत नहीं है की एक अंतहीन सूची को संभालने की जरूरत नहीं है।
सजातीय बिट्स और बाइट्स
सी में शोषण करने के लिए एक चीज क्योंकि इसमें प्रकार की सुरक्षा की एक अंतर्निहित कमी है, यह बिट्स और बाइट्स की विशेषताओं के आधार पर सजातीय चीजों को संग्रहीत करने का विचार है। स्मृति आबंटक और डेटा संरचना के बीच एक धब्बा के रूप में वहाँ अधिक है।
लेकिन चर आकार बातें, या यहाँ तक कि चीजें हैं जो केवल के एक झुंड के भंडारण कर सकता चर आकार हो, एक बहुरूपी की तरह Dogऔर Cat, कुशलता से करने के लिए मुश्किल है। आप इस धारणा से नहीं जा सकते हैं कि वे परिवर्तनशील आकार के हो सकते हैं और उन्हें एक साधारण यादृच्छिक-पहुंच वाले कंटेनर में संचित रूप से संग्रहीत कर सकते हैं क्योंकि एक तत्व से दूसरे तक जाने के लिए स्ट्राइड अलग हो सकता है। कुत्तों और बिल्लियों दोनों को शामिल करने वाली एक सूची को संग्रहीत करने के परिणामस्वरूप, आपको 3 अलग-अलग डेटा संरचना / आवंटन इंस्टेंसेस (कुत्तों के लिए एक, बिल्लियों के लिए एक, और आधार पॉइंटर या स्मार्ट पॉइंटर की बहुरूपिक सूची के लिए एक, या इससे भी बदतर का उपयोग करना पड़ सकता है) , प्रत्येक कुत्ते और बिल्ली को एक सामान्य-उद्देश्य के आवंटनकर्ता के खिलाफ आवंटित करें और उन्हें सभी मेमोरी में बिखेर दें), जो महंगा हो जाता है और अपने हिस्से को कई गुना कैश मिसेज़ देता है।
तो सी में उपयोग करने के लिए एक रणनीति, हालांकि यह कम प्रकार की समृद्धि और सुरक्षा पर आती है, बिट्स और बाइट्स के स्तर पर सामान्यीकरण करना है। आपको लगता है कि ग्रहण करने के लिए सक्षम हो सकता है Dogsऔर Catsबिट्स और बाइट्स की एक ही नंबर की आवश्यकता होती है, उसी के मैदान, एक समारोह सूचक तालिका के लिए एक ही सूचक है। लेकिन बदले में आप कम डेटा संरचनाओं को कोड कर सकते हैं, लेकिन जैसा कि महत्वपूर्ण है, इन सभी चीजों को कुशलतापूर्वक और संयोग से स्टोर करें। आप उस मामले में एनालॉग यूनियनों की तरह कुत्तों और बिल्लियों का इलाज कर रहे हैं (या आप वास्तव में एक संघ का उपयोग कर सकते हैं)।
और यह एक बड़ी लागत पर आता है सुरक्षा टाइप करें। अगर वहाँ एक बात मैं सी में कुछ और से अधिक याद आती है, यह प्रकार की सुरक्षा है। यह विधानसभा स्तर के करीब हो रहा है, जहां संरचनाएं केवल यह इंगित कर रही हैं कि कितनी मेमोरी आवंटित की गई है और प्रत्येक डेटा फ़ील्ड को कैसे संरेखित किया गया है। लेकिन यह वास्तव में सी का उपयोग करने के लिए मेरा नंबर एक कारण है। यदि आप वास्तव में मेमोरी लेआउट को नियंत्रित करने की कोशिश कर रहे हैं और जहां सब कुछ आवंटित किया गया है और जहां सब कुछ एक दूसरे के सापेक्ष संग्रहीत है, तो अक्सर यह बिट्स के स्तर पर चीजों के बारे में सोचने में मदद करता है और बाइट्स, और कितने बिट्स और बाइट्स आपको एक विशेष समस्या को हल करने की आवश्यकता है। वहाँ सी के प्रकार प्रणाली की गूंगा वास्तव में एक बाधा के बजाय फायदेमंद बन सकता है। आमतौर पर इससे निपटने के लिए बहुत कम डेटा प्रकारों को समाप्त करना होगा,
भ्रम / स्पष्ट दोहराव
अब मैं "नकल" का उपयोग ढीले अर्थों में उन चीजों के लिए कर रहा हूं जो शायद बेमानी भी नहीं हो सकती हैं। मैंने देखा है कि लोग "वास्तविक दोहराव" से "आकस्मिक / स्पष्ट" दोहराव जैसे शब्दों को अलग करते हैं। जिस तरह से मैं देख रहा हूं वह यह है कि कई मामलों में ऐसा कोई स्पष्ट अंतर नहीं है। मुझे "संभावित विशिष्टता" बनाम "संभावित दोहराव" जैसे भेद अधिक लगते हैं और यह किसी भी तरह से जा सकता है। यह अक्सर इस बात पर निर्भर करता है कि आप अपने डिजाइन और कार्यान्वयन को कैसे विकसित करना चाहते हैं और एक विशिष्ट उपयोग के मामले के लिए पूरी तरह से अनुरूप हैं। लेकिन मैंने अक्सर पाया है कि कोड दोहराव के बाद जो दिखाई दे सकता है वह बाद में पता चलता है कि सुधार के कई पुनरावृत्तियों के बाद अनावश्यक नहीं होगा।
का उपयोग कर realloc, एक साधारण बढ़ने योग्य सरणी कार्यान्वयन ले , के समतुल्य समकक्ष std::vector<int>। प्रारंभ में, यह std::vector<int>C ++ में उपयोग के साथ बेमानी हो सकता है। लेकिन, आप माप के माध्यम से पा सकते हैं कि, पहले से ही 64 बाइट्स का प्रचार करना फायदेमंद हो सकता है ताकि ढेर आवंटन की आवश्यकता के बिना सोलह 32-बिट पूर्णांक डालने की अनुमति दी जा सके। अब यह बेमानी नहीं है, कम से कम साथ नहीं std::vector<int>। और फिर आप कह सकते हैं, "लेकिन मैं इसे एक नए रूप में सामान्य कर सकता हूं।"SmallVector<int, 16> , और आप कर सकते हैं। लेकिन फिर मान लें कि यह आपको उपयोगी लगता है क्योंकि ये बहुत ही छोटे, अल्पकालिक सरणियों के लिए हैं, बजाय ढेर आवंटन पर सरणी क्षमता को चौगुनी करने के लिए 1.5 से बढ़ रही है (लगभग राशि है कि कईvectorकार्यान्वयन का उपयोग करते हुए) इस धारणा से काम करते हुए कि सरणी क्षमता हमेशा दो की शक्ति होती है। अब आपका कंटेनर वास्तव में अलग है, और शायद ऐसा कोई कंटेनर नहीं है। और हो सकता है कि आप प्रचार-प्रसार भारी, कस्टमाइज़ेशन व्यवहार, आदि को अनुकूलित करने के लिए अधिक से अधिक टेम्पलेट मापदंडों को जोड़कर इस तरह के व्यवहारों को सामान्य बनाने की कोशिश कर सकते हों, लेकिन उस बिंदु पर आपको साधारण सी की एक दर्जन लाइनों की तुलना में वास्तव में उपयोग में न आने वाली कुछ चीजें मिल सकती हैं। कोड।
और आप उस बिंदु पर भी पहुंच सकते हैं, जहां आपको एक डेटा संरचना की आवश्यकता होती है, जो 256-बिट संरेखित और गद्देदार स्मृति को आवंटित करता है, AVX 256 निर्देशों के लिए विशेष रूप से PODs का भंडारण करता है, आम मामला छोटे इनपुट आकार के लिए ढेर आवंटन से बचने के लिए 128 बाइट्स का प्रचार करता है, जब क्षमता में दोगुना हो जाता है। पूर्ण, और सरणी आकार से अधिक नहीं बल्कि सरणी क्षमता से अधिक होने वाले तत्वों के सुरक्षित अधिलेखित करने की अनुमति देता है। उस बिंदु पर यदि आप अभी भी सी कोड की एक छोटी मात्रा को डुप्लिकेट से बचने के लिए एक समाधान को सामान्य बनाने की कोशिश कर रहे हैं, तो प्रोग्रामिंग देवताओं को आपकी आत्मा पर दया आ सकती है।
तो कई बार ऐसा भी होता है, जहां शुरुआत में बेमानी दिखने लगती है, क्योंकि आप बेहतर और बेहतर और बेहतर उपयोग के लिए एक निश्चित मामले में, पूरी तरह से विशिष्ट और निरर्थक नहीं हैं। लेकिन यह केवल उन चीजों के लिए है जहां आप उन्हें एक विशिष्ट उपयोग के मामले में पूरी तरह से तैयार कर सकते हैं। कभी-कभी हमें बस एक "सभ्य" चीज की आवश्यकता होती है जो हमारे उद्देश्य के लिए सामान्यीकृत होती है, और वहां मुझे बहुत सामान्यीकृत डेटा संरचनाओं से सबसे अधिक लाभ होता है। लेकिन एक विशेष उपयोग के मामले के लिए पूरी तरह से बनाई गई असाधारण चीजों के लिए, "सामान्य उद्देश्य" और "पूरी तरह से मेरे उद्देश्य के लिए" का विचार बहुत असंगत होने लगता है।
PODs और आदिम
अब C में, मैं अक्सर PODs और विशेष रूप से आदिम को डेटा संरचनाओं में संग्रहीत करने के लिए बहाने ढूंढता हूं जब भी संभव हो। यह एक विरोधी पैटर्न की तरह लग सकता है, लेकिन मैंने वास्तव में इसे अनजाने में उन चीजों के प्रकारों पर कोड की स्थिरता को सुधारने में मददगार पाया है जो मैं सी ++ में अधिक बार करता था।
एक साधारण उदाहरण शॉर्ट स्ट्रिंग्स को इंटर्न कर रहा है (जैसा कि आमतौर पर खोज कुंजी के लिए उपयोग किए जाने वाले स्ट्रिंग्स के साथ होता है - वे बहुत कम होते हैं)। इन सभी चर-लंबाई के तारों से निपटने में परेशान क्यों होते हैं जिनके आकार रनटाइम में भिन्न होते हैं, गैर-तुच्छ निर्माण और विनाश का अर्थ है (क्योंकि हमें आवंटित और मुक्त करने की आवश्यकता हो सकती है)? कैसे के बारे में सिर्फ एक केंद्रीय डेटा संरचना में इन चीजों को स्टोर करते हैं, जैसे कि थ्रेड इंटर्निंग के लिए डिज़ाइन किया गया थ्रेड-सेफ ट्राई या हैश टेबल, और फिर एक सादे पुराने int32_tया उन स्ट्रिंग्स को देखें :
struct IternedString
{
int32_t index;
};
... हमारे हैश टेबल में, लाल-काले पेड़, सूची छोड़ें, आदि, अगर हमें लेक्सोग्राफिक छंटाई की आवश्यकता नहीं है? अब हमारी सभी अन्य डेटा संरचनाएं जिन्हें हमने 32-बिट पूर्णांक के साथ काम करने के लिए कोडित किया था, अब इन इंटर्न स्ट्रिंग स्ट्रिंग को संग्रहीत कर सकते हैं जो प्रभावी रूप से सिर्फ 32-बिट हैं ints। और मैंने अपने उपयोग के मामलों में कम से कम पाया है (मैं सिर्फ मेरा डोमेन हो सकता हूं क्योंकि मैं रेस्ट्रिंग, मेष प्रसंस्करण, छवि प्रसंस्करण, कण प्रणाली जैसे क्षेत्रों में काम करता हूं, स्क्रिप्टिंग भाषाओं के लिए बाध्य हूं, निम्न-स्तरीय मल्टीथ्रेडेड GUI किट कार्यान्वयन, आदि)। निम्न-स्तरीय चीजें लेकिन एक OS के रूप में निम्न-स्तर नहीं), कि संयोग से कोड अधिक कुशल और सरल हो जाता है जैसे कि इस तरह की चीजों के लिए सूचकांकों को संग्रहीत करना। ऐसा लगता है कि मैं अक्सर काम कर रहा हूँ, बस int32_tऔर के साथ, समय का 75% कहते हैंfloat32 मेरी गैर-तुच्छ डेटा संरचनाओं में, या केवल एक ही आकार वाली चीजें (लगभग हमेशा 32-बिट) संग्रहीत करने के लिए।
और स्वाभाविक रूप से यदि यह आपके मामले के लिए लागू होता है, तो आप विभिन्न डेटा प्रकारों के लिए कई डेटा संरचना कार्यान्वयन होने से बच सकते हैं, क्योंकि आप पहली बार इतने कम काम कर रहे होंगे।
परीक्षण और विश्वसनीयता
एक आखिरी चीज़ जो मैं पेश करूंगा, और यह सभी के लिए नहीं हो सकती है, वह है उन डेटा संरचनाओं के लिए लेखन परीक्षण का पक्ष लेना। उन्हें वास्तव में कुछ अच्छा बनाओ। सुनिश्चित करें कि वे अल्ट्रा विश्वसनीय हैं।
कुछ मामूली कोड दोहराव उन मामलों में बहुत अधिक क्षम्य हो जाते हैं, क्योंकि कोड डुप्लीकेशन केवल एक रखरखाव का बोझ है यदि आपको डुप्लिकेट कोड में कैस्केडिंग परिवर्तन करना है। आप इस तरह के अतिरेक कोड को बदलने के लिए प्रमुख कारणों में से एक को खत्म करते हैं, यह सुनिश्चित करने के लिए कि यह अति विश्वसनीय है और वास्तव में अच्छी तरह से अनुकूल है कि यह क्या करने की कोशिश नहीं कर रहा है।
सौंदर्यशास्त्र की मेरी भावना पिछले कुछ वर्षों में बदल गई है। मुझे अब चिढ़ नहीं है क्योंकि मैं एक पुस्तकालय लागू कर रहा हूँ डॉट उत्पाद या कुछ तुच्छ एसएलएल तर्क जो पहले से ही दूसरे में लागू है। मैं केवल तब चिढ़ जाता हूं जब चीजें खराब परीक्षण और अविश्वसनीय होती हैं, और मैंने पाया है कि बहुत अधिक उत्पादक मानसिकता। मैंने वास्तव में कोड आधारों के साथ निपटाया है जो डुप्लिकेट कोड के माध्यम से बग्स को डुप्लिकेट करता है, और कॉपी-एंड-पेस्ट कोडिंग के सबसे खराब मामलों को देखा है जो एक केंद्रीय स्थान पर एक तुच्छ परिवर्तन होना चाहिए था जो कई के लिए एक त्रुटि-प्रवण कैस्केडिंग परिवर्तन में बदल जाता है। फिर भी उनमें से कई बार, यह खराब परीक्षण का परिणाम था, कोड के असफल होने के कारण यह विश्वसनीय और अच्छा था कि यह पहले स्थान पर क्या कर रहा था। इससे पहले कि जब मैं छोटी गाड़ी विरासत कोडबेस में काम कर रहा था, मेरे दिमाग में कोड डुप्लिकेट के सभी प्रकार जुड़े हुए हैं, बग्स की नकल करने और कैस्केडिंग परिवर्तनों की बहुत अधिक संभावना है। फिर भी एक लघु पुस्तकालय जो एक काम बहुत अच्छी तरह से और मज़बूती से करता है, भविष्य में बदलने के बहुत कम कारण मिलेंगे, भले ही इसमें कुछ निरर्थक दिखने वाला कोड यहां और वहां हो। मेरी प्राथमिकताएं तब वापस आ गईं, जब नकल ने मुझे खराब गुणवत्ता और परीक्षण की कमी से अधिक परेशान किया। ये बाद की बातें सर्वोच्च प्राथमिकता होनी चाहिए।
मिनिमलिज़्म के लिए कोड दोहराव?
यह एक मजाकिया विचार है जो मेरे सिर में है, लेकिन एक ऐसे मामले पर विचार करें जहां हम एक सी और सी ++ लाइब्रेरी का सामना कर सकते हैं जो लगभग एक ही काम करते हैं: दोनों की लगभग एक ही कार्यक्षमता है, त्रुटि से निपटने की समान मात्रा, एक काफी नहीं है अन्य की तुलना में अधिक कुशल, आदि और सबसे महत्वपूर्ण बात, दोनों सक्षम रूप से कार्यान्वित, अच्छी तरह से परीक्षण किए गए, और विश्वसनीय हैं। दुर्भाग्य से मुझे यहाँ काल्पनिक रूप से बोलना पड़ता है क्योंकि मैंने कभी भी किसी भी पक्ष के साथ तुलना के करीब नहीं पाया। लेकिन इस साइड-बाय-साइड तुलना के बारे में मैंने जो सबसे करीबी चीजें पाईं, उनमें अक्सर सी लाइब्रेरी बहुत अधिक थी, सी ++ समकक्ष (कभी-कभी इसके कोड आकार के 1/10 वें) की तुलना में बहुत छोटी थी।
और मुझे विश्वास है कि इसका कारण यह है कि, एक समस्या को हल करने के लिए सामान्य तरीके से, जो एक सटीक उपयोग मामले के बजाय उपयोग के मामलों की सबसे विस्तृत श्रृंखला को संभालता है, कोड की सैकड़ों से हजारों लाइनों की आवश्यकता हो सकती है, जबकि बाद वाले को केवल आवश्यकता हो सकती है एक दर्ज़न। अतिरेक के बावजूद, और इस तथ्य के बावजूद कि जब मानक डेटा संरचना प्रदान करने की बात आती है, तो सी मानक लाइब्रेरी अव्यवस्थित है, वही समस्याओं को हल करने के लिए अक्सर मानव हाथों में कम कोड का उत्पादन होता है, और मुझे लगता है कि यह मुख्य रूप से है इन दो भाषाओं के बीच मानव प्रवृत्ति में अंतर। एक बहुत विशिष्ट उपयोग के मामले के खिलाफ चीजों को हल करने को बढ़ावा देता है, दूसरा उपयोग के मामलों की व्यापक रेंज के खिलाफ अधिक सार और सामान्य समाधान को बढ़ावा देता है, लेकिन इन don का अंतिम परिणाम '
मैं दूसरे दिन गिथब पर किसी की किरण को देख रहा था और इसे C ++ में लागू किया गया था और इसके लिए आवश्यक था, एक खिलौना किरण के लिए इतना कोड। और मैंने उस समय को कोड को देखने में खर्च नहीं किया था, लेकिन वहां सामान्य-प्रयोजन संरचनाओं का एक बोट लोड था जो एक तरह से संभाल रहे थे, जिस तरह से एक रेट्रेटर की आवश्यकता होगी। और मैं कोडिंग की उस शैली को पहचानता हूं क्योंकि मैं सी ++ का उपयोग उसी तरह के सुपर बॉटम-अप फैशन में करता था, जो कि बहुत सामान्य-उद्देश्य डेटा संरचनाओं के पूर्ण विकसित पुस्तकालय को बनाने पर ध्यान केंद्रित करता है, जो कि ऊपर और बाहर से आगे की तरफ जाता है। हाथ में समस्या और फिर दूसरी वास्तविक समस्या से निपटना। लेकिन जब वे सामान्य संरचनाएँ यहाँ और वहाँ कुछ अतिरेक को खत्म कर सकती हैं और नए संदर्भों में बहुत अधिक पुन: उपयोग करेंगी, बदले में वे अनावश्यक कोड / कार्यक्षमता के एक बोटलोड के साथ अतिरेक का एक छोटा सा आदान-प्रदान करके परियोजना को बहुत बढ़ाते हैं, और बाद वाले को पूर्व की तुलना में बनाए रखना आसान नहीं है। इसके विपरीत, मुझे अक्सर इसे बनाए रखना कठिन लगता है, क्योंकि कुछ सामान्य चीजों के डिजाइन को बनाए रखना कठिन होता है, जिसमें संभव की व्यापक श्रेणी के खिलाफ डिजाइन निर्णयों को सख्त करना होता है।