तो किस बिंदु पर एक वर्ग अपरिवर्तनीय होने के लिए बहुत जटिल हो जाता है?
मेरी राय में यह छोटी कक्षाओं को भाषाओं में अपरिवर्तनीय बनाने के लिए परेशान करने लायक नहीं है , जैसे आप दिखा रहे हैं। मैं यहां छोटे का उपयोग कर रहा हूं और जटिल नहीं , क्योंकि भले ही आप उस क्षेत्र में दस फ़ील्ड जोड़ते हैं और यह वास्तव में उन पर फैंसी ऑपरेशन करता है, मुझे संदेह है कि यह किलोबाइट लेने जा रहा है अकेले मेगाबाइट्स को अकेले गीगाबाइट दें, इसलिए किसी भी फ़ंक्शन को आपके उदाहरणों का उपयोग करके क्लास केवल मूल को संशोधित करने से बचने के लिए पूरे ऑब्जेक्ट की एक सस्ती प्रतिलिपि बना सकता है अगर वह बाहरी दुष्प्रभावों के कारण से बचना चाहता है।
लगातार डेटा संरचनाएं
जहाँ मुझे पता चलता है कि अपरिवर्तनीयता के लिए व्यक्तिगत उपयोग बड़े, केंद्रीय डेटा संरचनाओं के लिए है, जो कि आपके द्वारा दिखाए जा रहे वर्ग के उदाहरणों की तरह नन्हा डेटा के एक समूह को एकत्रित करते हैं, जैसे कि एक मिलियन को संग्रहीत करता है NamedThings
। एक सतत डेटा संरचना से संबंधित है जो अपरिवर्तनीय है और एक इंटरफ़ेस के पीछे है जो केवल रीड-ओनली एक्सेस की अनुमति देता है, जो तत्व कंटेनर से संबंधित हैं वे तत्व वर्ग ( NamedThing
) से निपटने के बिना अपरिवर्तनीय हो जाते हैं ।
सस्ते कॉपियां
लगातार डेटा संरचना इसके क्षेत्रों को रूपांतरित करने और अद्वितीय बनाने की अनुमति देती है, बिना संशोधन को मूल रूप से संशोधित किए बिना डेटा संरचना को इसकी संपूर्णता में कॉपी करने के लिए। वही इसका असली सौंदर्य है। यदि आप ऐसे कार्यों को करना चाहते हैं, जो साइड इफेक्ट्स से बचते हैं, जो उस डेटा संरचना को इनपुट करते हैं, जो मेमोरी की गीगाबाइट लेता है और केवल मेगाबाइट की मेमोरी के मूल्य को संशोधित करता है, तो आपको इनपुट को छूने से बचने और एक नया वापस करने के लिए पूरी फ्रिक करने वाली चीज़ को कॉपी करना होगा उत्पादन। यह या तो गीगाबाइट की नकल करने से साइड इफेक्ट्स से बचने या उस परिदृश्य में साइड इफेक्ट्स का कारण बनता है, जिससे आपको दो अप्रिय विकल्पों के बीच चयन करना होगा।
लगातार डेटा संरचना के साथ, यह आपको इस तरह के एक फ़ंक्शन को लिखने और संपूर्ण डेटा संरचना की प्रतिलिपि बनाने से बचने की अनुमति देता है, केवल आउटपुट के लिए अतिरिक्त मेमोरी की मेगाबाइट की आवश्यकता होती है यदि आपका फ़ंक्शन केवल मेगाबाइट के मेमोरी के मूल्य को बदल देता है।
बोझ
बोझ के लिए, मेरे मामले में कम से कम एक तत्काल है। मुझे उन बिल्डरों की आवश्यकता है जिनके बारे में लोग या "ग्राहक" बात कर रहे हैं क्योंकि मैं उन्हें बिना किसी स्पर्श के उस बड़े पैमाने पर डेटा संरचना में प्रभावी ढंग से परिवर्तन व्यक्त करने में सक्षम होने के लिए कहता हूं। इस तरह कोड:
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
... फिर इस तरह लिखना होगा:
ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
// Grab a "transient" (builder) list we can modify:
TransientList<Stuff> transient(stuff);
// Transform stuff in the range, [first, last)
// for the transient list.
for (; first != last; ++first)
transform(transient[first]);
// Commit the modifications to get and return a new
// immutable list.
return stuff.commit(transient);
}
लेकिन कोड की उन दो अतिरिक्त लाइनों के बदले में, फ़ंक्शन अब एक ही मूल सूची के साथ थ्रेड्स पर कॉल करने के लिए सुरक्षित है, यह कोई साइड इफेक्ट्स का कारण नहीं बनता है, आदि। यह भी इस ऑपरेशन को एक पूर्ववत उपयोगकर्ता कार्रवाई करने के लिए वास्तव में आसान बनाता है पूर्ववत बस पुरानी सूची की एक सस्ती उथली प्रति स्टोर कर सकते हैं।
अपवाद-सुरक्षा या त्रुटि पुनर्प्राप्ति
इन जैसे संदर्भों में लगातार डेटा स्ट्रक्चर्स से मैंने जितना भी किया, सभी को लाभ नहीं हो सकता है (मैंने पूर्ववत प्रणालियों और गैर-विनाशकारी संपादन में उनके लिए इतना उपयोग पाया जो कि मेरे वीएफएक्स डोमेन में केंद्रीय अवधारणाएं हैं), लेकिन सिर्फ एक चीज के बारे में विचार करने के लिए हर कोई अपवाद-सुरक्षा या त्रुटि पुनर्प्राप्ति है ।
यदि आप मूल म्यूटिंग फ़ंक्शन को अपवाद-सुरक्षित बनाना चाहते हैं, तो उसे रोलबैक तर्क की आवश्यकता है, जिसके लिए सरलतम कार्यान्वयन के लिए संपूर्ण प्रतिलिपि की आवश्यकता होती है सूची :
void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
// Make a copy of the whole massive gigabyte-sized list
// in case we encounter an exception and need to rollback
// changes.
MutList<Stuff> old_stuff = stuff;
try
{
// Transform stuff in the range, [first, last).
for (; first != last; ++first)
transform(stuff[first]);
}
catch (...)
{
// If the operation failed and ran into an exception,
// swap the original list with the one we modified
// to "undo" our changes.
stuff.swap(old_stuff);
throw;
}
}
इस बिंदु पर अपवाद-सुरक्षित परिवर्तनशील संस्करण और भी अधिक कम्प्यूटेशनल रूप से महंगा है और यकीनन "बिल्डर" का उपयोग करके अपरिवर्तनीय संस्करण की तुलना में सही ढंग से लिखना कठिन है। और बहुत सारे सी ++ डेवलपर्स सिर्फ अपवाद-सुरक्षा की उपेक्षा करते हैं और शायद यह उनके डोमेन के लिए ठीक है, लेकिन मेरे मामले में मैं अपवाद के मामले में भी अपने कोड कार्यों को सही ढंग से सुनिश्चित करना पसंद करता हूं (यहां तक कि परीक्षण भी लिखना जो अपवाद को परीक्षण करने के लिए जानबूझकर अपवाद फेंकते हैं। सुरक्षा), और जो इसे बनाता है, इसलिए मुझे किसी भी साइड इफेक्ट का रोलबैक करने में सक्षम होना पड़ता है, अगर कोई भी कार्य फ़ंक्शन में आधे रास्ते का कारण बनता है तो कुछ भी फेंकता है।
जब आप अपवाद-सुरक्षित होना चाहते हैं और आपके आवेदन के दुर्घटनाग्रस्त होने और जलने के बिना त्रुटियों से उबरते हैं, तो आपको किसी भी साइड इफेक्ट के कारण / पूर्ववत करना पड़ता है जो त्रुटि / अपवाद की स्थिति में हो सकता है। और बिल्डर वास्तव में कम्प्यूटेशनल समय के साथ लागत की तुलना में अधिक प्रोग्रामर समय बचा सकता है क्योंकि: ...
आपको किसी फ़ंक्शन में बैक साइड इफेक्ट्स को रोल करने के बारे में चिंता करने की ज़रूरत नहीं है जो किसी भी कारण नहीं है!
तो मूल प्रश्न पर वापस जाएं:
किस बिंदु पर अपरिवर्तनीय कक्षाएं बोझ बन जाती हैं?
वे हमेशा उन भाषाओं में बोझ होते हैं जो अपरिवर्तनीयता की तुलना में अधिक परिवर्तनशीलता के आसपास घूमती हैं, यही कारण है कि मुझे लगता है कि आपको उनका उपयोग करना चाहिए जहां लाभ लागत को काफी कम कर देते हैं। लेकिन बड़े पर्याप्त डेटा संरचनाओं के लिए व्यापक स्तर पर, मेरा मानना है कि ऐसे कई मामले हैं जहां यह एक योग्य व्यापार-बंद है।
मेरा भी, मेरे पास केवल कुछ अपरिवर्तनीय डेटा प्रकार हैं, और वे सभी विशाल डेटा संरचनाएं हैं जिनका उद्देश्य बड़ी संख्या में तत्वों (छवि / बनावट के पिक्सेल, इकाइयां और ईसीएस के घटक, और कोने / किनारों / बहुभुज) को संग्रहीत करना है। a mesh)।