C ++ अपवादों का अधिक बार उपयोग करना पसंद करता है।
मैं वास्तव में कुछ मामलों में ऑब्जेक्टिव-सी से कम का सुझाव दूंगा क्योंकि सी ++ मानक लाइब्रेरी आमतौर पर प्रोग्रामर त्रुटियों पर नहीं फेंकती है, जैसे कि इसके सबसे सामान्य मामले डिजाइन फॉर्म (में operator[]
, यानी) में रैंडम-एक्सेस सीक्वेंस के आउट-ऑफ-बाउंड एक्सेस की तरह। अमान्य पुनरावृत्ति को रोकने के लिए प्रयास कर रहा है। भाषा किसी सरणी को सीमा से बाहर जाने, या अशक्त सूचक, या इस प्रकार की किसी भी चीज़ को एक्सेस करने पर नहीं फेंकती है।
प्रोग्रामर गलतियों को बड़े पैमाने पर अपवाद-हैंडलिंग समीकरण से बाहर ले जाना वास्तव में त्रुटियों की एक बहुत बड़ी श्रेणी को दूर करता है जो अन्य भाषाएं अक्सर जवाब देती हैं throwing
। C ++ को जाता है assert
(जो रिलीज / प्रोडक्शन बिल्ड, केवल डिबग बिल्ड) में संकलित नहीं होता है या ऐसे मामलों में सिर्फ गड़बड़ (अक्सर दुर्घटनाग्रस्त) हो जाता है, शायद इसलिए क्योंकि भाषा ऐसे रनटाइम चेक की लागत को लागू नहीं करना चाहती है जब तक कि प्रोग्रामर विशेष रूप से कोड लिखकर लागतों का भुगतान नहीं करना चाहता है जब तक कि ऐसे चेक स्वयं / स्वयं नहीं करता है, तब तक ऐसी प्रोग्रामर गलतियों का पता लगाना आवश्यक है।
सी + + कोडिंग मानकों में ऐसे मामलों में अपवादों से बचने के लिए सटर भी प्रोत्साहित करता है:
एक प्रोग्रामिंग त्रुटि की रिपोर्ट करने के लिए एक अपवाद का उपयोग करने का प्राथमिक नुकसान यह है कि आप वास्तव में स्टैक अनइंडिंग नहीं चाहते हैं जब आप डिबगर को उस सटीक लाइन पर लॉन्च करना चाहते हैं जहां उल्लंघन का पता चला था, तो लाइन की स्थिति बरकरार है। संक्षेप में: ऐसी त्रुटियां हैं जिन्हें आप जानते हैं कि ऐसा हो सकता है (आइटम 69 से 75 देखें)। बाकी सब के लिए जो नहीं होना चाहिए, और यह प्रोग्रामर की गलती है अगर यह करता है, तो है assert
।
यह नियम आवश्यक रूप से पत्थर में सेट नहीं है। कुछ और मिशन-क्रिटिकल मामलों में, उपयोग करने, कहने के लिए, रैपर और एक कोडिंग मानक, जो समान रूप से लॉग throw
इन करते हैं, जहां प्रोग्रामर की गलतियाँ होती हैं और प्रोग्रामर की ग़लतियों की उपस्थिति में, जैसे किसी चीज़ को अमान्य मानने या उसे सीमा से बाहर पहुँचने की कोशिश करने की उपस्थिति के लिए बेहतर हो सकता है, क्योंकि यदि सॉफ़्टवेयर का मौका है तो उन मामलों में पुनर्प्राप्त करने में विफल रहना बहुत महंगा हो सकता है। लेकिन कुल मिलाकर भाषा का अधिक सामान्य उपयोग प्रोग्रामर गलतियों के सामने न फेंकने के पक्ष में है।
बाहरी अपवाद
जहां मैं अपवादों को C ++ में सबसे अधिक बार प्रोत्साहित करता हूं (मानक समिति के अनुसार, उदाहरण के लिए) "बाहरी अपवाद" के लिए है, जैसा कि कार्यक्रम के बाहर कुछ बाहरी स्रोत में अप्रत्याशित परिणाम है। एक उदाहरण स्मृति को आवंटित करने में विफल हो रहा है। सॉफ़्टवेयर चलाने के लिए आवश्यक एक महत्वपूर्ण फ़ाइल खोलने में अन्य विफल हो रहा है। एक अन्य आवश्यक सर्वर से कनेक्ट करने में विफल हो रहा है। एक अन्य एक उपयोगकर्ता है जो एक ऑपरेशन को रद्द करने के लिए एक गर्भपात बटन को ठेला करता है जिसका सामान्य मामला निष्पादन पथ इस बाहरी रुकावट को अनुपस्थित करने में सफल होने की उम्मीद करता है। ये सभी चीजें तत्काल सॉफ्टवेयर और प्रोग्रामर्स के नियंत्रण से बाहर हैं जिन्होंने इसे लिखा था। वे बाहरी स्रोतों से अप्रत्याशित परिणाम हैं जो ऑपरेशन को रोकते हैं (जो वास्तव में मेरी पुस्तक * में एक अविभाज्य लेनदेन के रूप में सोचा जाना चाहिए) सफल होने में सक्षम होने से।
लेन-देन
मैं अक्सर एक try
ब्लॉक को "लेनदेन" के रूप में देखने के लिए प्रोत्साहित करता हूं क्योंकि लेनदेन को एक पूरे के रूप में सफल होना चाहिए या एक पूरे के रूप में विफल होना चाहिए। यदि हम कुछ करने की कोशिश कर रहे हैं और यह आधे रास्ते में विफल हो जाता है, तो प्रोग्राम स्टेट में किए गए किसी भी साइड इफेक्ट्स / म्यूटेशन को आमतौर पर सिस्टम को एक वैध स्थिति में वापस लाने के लिए वापस लेने की आवश्यकता होती है जैसे कि लेनदेन को कभी भी निष्पादित नहीं किया गया था; आरडीबीएमएस के रूप में, जो एक क्वेरी को आधे से संसाधित करने में विफल रहता है, उसे डेटाबेस की अखंडता से समझौता नहीं करना चाहिए। यदि आप उक्त लेन-देन में सीधे प्रोग्राम स्टेट को म्यूट करते हैं, तो आपको एक त्रुटि का सामना करने पर "अनम्यूट" करना होगा (और यहां गुंजाइश गार्ड RAII के साथ उपयोगी हो सकता है)।
बहुत सरल विकल्प मूल कार्यक्रम की स्थिति को बदलना नहीं है ; आप इसकी एक प्रति म्यूट कर सकते हैं और फिर, अगर यह सफल होता है, तो मूल के साथ प्रतिलिपि स्वैप करें (यह सुनिश्चित करना कि स्वैप फेंक नहीं सकता)। यदि यह विफल रहता है, तो प्रतिलिपि को छोड़ दें। यदि आप सामान्य रूप से त्रुटि से निपटने के लिए अपवादों का उपयोग नहीं करते हैं तो भी यह लागू होता है। यदि एक त्रुटि का सामना करने से पहले प्रोग्राम स्टेट म्यूटेशन हुआ है, तो एक "लेन-देन" मानसिकता उचित पुनर्प्राप्ति के लिए महत्वपूर्ण है। यह या तो एक पूरे के रूप में सफल होता है या पूरे के रूप में विफल रहता है। यह अपने उत्परिवर्तन को बनाने में आधा सफल नहीं होता है।
जब मैं प्रोग्रामर को ठीक से त्रुटि या अपवाद से निपटने के तरीके के बारे में पूछ रहा होता हूं, तो यह विचित्र रूप से अक्सर चर्चा किए जाने वाले विषयों में से एक है, फिर भी उन सभी में से किसी भी सॉफ्टवेयर में सही तरीके से प्राप्त करना सबसे कठिन है जो सीधे कई में से कार्यक्रम को बदलना चाहते हैं इसके संचालन। शुद्धता और अपरिवर्तनीयता यहां अपवाद-सुरक्षा प्राप्त करने में मदद कर सकती है, जितना कि वे थ्रेड-सेफ्टी के साथ मदद करते हैं, एक उत्परिवर्तन / बाहरी साइड इफेक्ट के रूप में जो वापस होने की आवश्यकता नहीं है।
प्रदर्शन
अपवादों का उपयोग करने या न करने के लिए एक अन्य मार्गदर्शक कारक प्रदर्शन है, और मुझे कुछ जुनूनी, पैसा-चुटकी, काउंटर-उत्पादक तरीके से मतलब नहीं है। बहुत सारे C ++ कंपाइलर लागू होते हैं जिन्हें "जीरो-कॉस्ट एक्सेप्शन हैंडलिंग" कहा जाता है।
यह एक त्रुटि-मुक्त निष्पादन के लिए शून्य रनवे ओवरहेड प्रदान करता है, जो सी रिटर्न-मूल्य त्रुटि हैंडलिंग से भी आगे निकल जाता है। ट्रेड-ऑफ के रूप में, एक अपवाद के प्रचार में एक बड़ा उपरि है।
मैंने इसके बारे में जो पढ़ा है, उसके अनुसार, यह आपके सामान्य मामले के निष्पादन पथ को ओवरहेड की आवश्यकता नहीं है (ओवरहेड भी नहीं है जो सामान्य रूप से सी-स्टाइल एरर कोड हैंडलिंग और प्रचार के साथ है), असाधारण रास्तों की ओर लागत को कम करने के बदले में। जिसका मतलब throwing
अब पहले से कहीं ज्यादा महंगा है)।
"महंगी" थोड़ा मुश्किल है, लेकिन शुरुआत के लिए, आप शायद किसी तंग लूप में एक लाख बार फेंकना नहीं चाहते हैं। इस तरह का डिज़ाइन मानता है कि अपवाद हर समय बाएँ और दाएँ नहीं हो रहे हैं।
गैर त्रुटियाँ
और वह प्रदर्शन बिंदु मुझे गैर-त्रुटियों के लिए लाता है, जो आश्चर्यजनक रूप से फजी है अगर हम अन्य भाषाओं के सभी प्रकारों को देखते हैं। लेकिन मैं ऊपर उल्लिखित शून्य-लागत ईएच डिजाइन को देखते हुए कहूंगा कि आप निश्चित रूप throw
से एक सेट में नहीं मिल रही कुंजी के जवाब में नहीं चाहते हैं । क्योंकि न केवल यह है कि यकीनन एक गैर-त्रुटि (कुंजी की खोज करने वाला व्यक्ति सेट का निर्माण कर सकता है और उन कुंजियों की खोज करने की उम्मीद कर सकता है जो हमेशा मौजूद नहीं होते हैं), लेकिन यह उस संदर्भ में बहुत महंगा होगा।
उदाहरण के लिए, एक सेट प्रतिच्छेदन फ़ंक्शन दो सेटों के माध्यम से लूप करना चाहता है और उन कुंजियों की खोज कर सकता है जो उनके पास हैं। यदि एक कुंजी खोजने में विफल रहा है threw
, तो आप के माध्यम से पाशन होगा और आधे या अधिक पुनरावृत्तियों में अपवादों का सामना कर सकता है:
Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
Set<int> intersection;
for (int key: a)
{
try
{
b.find(key);
intersection.insert(other_key);
}
catch (const KeyNotFoundException&)
{
// Do nothing.
}
}
return intersection;
}
यह उपरोक्त उदाहरण बिल्कुल हास्यास्पद और अतिरंजित है, लेकिन मैंने देखा है, उत्पादन कोड में, सी ++ में अपवादों का उपयोग करते हुए अन्य भाषाओं से आने वाले कुछ लोग कुछ इस तरह से हैं, और मुझे लगता है कि यह एक व्यावहारिक रूप से व्यावहारिक कथन है कि यह अपवादों का उचित उपयोग नहीं है। C ++ में। ऊपर एक और संकेत यह है कि आप देखेंगे कि catch
ब्लॉक के पास करने के लिए कुछ भी नहीं है और केवल ऐसे किसी अपवाद को जबरन अनदेखा करने के लिए लिखा गया है, और यह आमतौर पर एक संकेत है (हालांकि गारंटर नहीं) कि अपवादों को संभवतः C ++ में बहुत उपयुक्त रूप से उपयोग नहीं किया जा रहा है।
उन प्रकार के मामलों के लिए, कुछ प्रकार के रिटर्न वैल्यू विफलता का संकेत देते हैं (कुछ भी false
एक अमान्य पुनरावृत्ति करने वाले से nullptr
या जो भी संदर्भ में समझ में आता है) आमतौर पर कहीं अधिक उपयुक्त होता है, और अक्सर गैर-त्रुटि प्रकार के बाद भी अधिक व्यावहारिक और उत्पादक होता है मामला आमतौर पर एनालॉग catch
साइट तक पहुंचने के लिए कुछ स्टैक अनइंडिंग प्रक्रिया के लिए कॉल नहीं करता है ।
प्रशन
यदि मुझे अपवादों से बचने के लिए चुना जाता है, तो मुझे आंतरिक त्रुटि झंडे के साथ जाना होगा। क्या यह संभालना बहुत अधिक परेशान करेगा, या यह अपवादों से बेहतर काम करेगा? दोनों मामलों की तुलना सबसे अच्छा जवाब होगा।
C ++ में एकमुश्त अपवाद से बचना मेरे लिए बेहद प्रति-उत्पादक है, जब तक कि आप कुछ एम्बेडेड सिस्टम या किसी विशेष प्रकार के मामले में काम नहीं कर रहे हैं जो उनके उपयोग को मना करता है (जिस स्थिति में आपको सभी से बचने के लिए अपने रास्ते से बाहर जाना होगा। पुस्तकालय और भाषा की कार्यक्षमता जो अन्यथा throw
सख्ती से उपयोग की तरह होगी nothrow
new
)।
यदि आपको किसी भी कारण से अपवादों से बचना है (उदाहरण के लिए: एक मॉड्यूल की सी एपीआई सीमाओं पर काम करना, जिसका सी एपीआई आप निर्यात करते हैं), तो कई लोग मुझसे असहमत हो सकते हैं, लेकिन मैं वास्तव में ओपनगेल जैसी वैश्विक त्रुटि हैंडलर / स्थिति का उपयोग करने का सुझाव दूंगा glGetError()
। आप इसे थ्रेड-स्थानीय संग्रहण का उपयोग कर सकते हैं ताकि प्रति थ्रेड में एक अद्वितीय त्रुटि स्थिति हो।
इसके लिए मेरा तर्क यह है कि मैं उत्पादन वातावरण में टीमों को पूरी तरह से सभी संभावित त्रुटियों की जांच करने के लिए अभ्यस्त हूं, दुर्भाग्य से, जब त्रुटि कोड वापस आ जाते हैं। यदि वे पूरी तरह से थे, तो कुछ सी एपीआई हर एक सी एपीआई कॉल के साथ एक त्रुटि का सामना कर सकते हैं, और पूरी तरह से जाँच के लिए कुछ की आवश्यकता होगी:
if ((err = ApiCall(...)) != success)
{
// Handle error
}
... कोड की लगभग हर एक पंक्ति में एपीआई को चेक करने के लिए इस तरह के चेक की आवश्यकता होती है। फिर भी मुझे टीमों के साथ काम करने का सौभाग्य नहीं मिला है। वे अक्सर ऐसी त्रुटियों को अनदेखा करते हैं आधा, कभी-कभी सबसे अधिक, समय का भी। यह मेरे लिए सबसे बड़ी अपील है। यदि हम इस एपीआई को लपेटते हैं और इसे throw
त्रुटि का सामना करने पर समान रूप से बनाते हैं , तो अपवाद को संभवतः नजरअंदाज नहीं किया जा सकता है , और मेरे विचार में, और अनुभव, यही वह जगह है जहां अपवादों की श्रेष्ठता निहित है।
लेकिन यदि अपवादों का उपयोग नहीं किया जा सकता है, तो वैश्विक, प्रति-थ्रेड त्रुटि स्थिति में कम से कम लाभ है (मेरे पास त्रुटि कोड की तुलना में बहुत बड़ा) कि यह पूर्व त्रुटि को पकड़ने का एक मौका हो सकता है, जबकि यह बाद में कुछ एक मैला कोडबेश में घटित होने के बजाए इसे गायब कर दिया और जो हुआ उसके बारे में हमें पूरी तरह से बेखबर छोड़ दिया। त्रुटि कुछ लाइनों से पहले या पिछले फ़ंक्शन कॉल में हो सकती है, लेकिन बशर्ते सॉफ़्टवेयर अभी तक क्रैश नहीं हुआ है, हम अपने तरीके से पीछे की ओर काम करना शुरू कर सकते हैं और यह पता लगा सकते हैं कि यह कहां और क्यों हुआ है।
यह मुझे लगता है कि चूंकि संकेत दुर्लभ हैं, मुझे अपवादों से बचने के लिए चुनने पर आंतरिक त्रुटि झंडे के साथ जाना होगा।
मैं जरूरी नहीं कहूंगा कि संकेत दुर्लभ हैं। C ++ 11 और उसके बाद के कंटेनर के अंतर्निहित डेटा पॉइंटर्स और एक नए nullptr
कीवर्ड को प्राप्त करने के लिए अभी भी विधियाँ हैं । आम तौर पर कच्चे संकेत का उपयोग करने के लिए नासमझ माना जाता है / स्मृति का प्रबंधन यदि आप unique_ptr
इसके बजाय कुछ का उपयोग कर सकते हैं तो यह महत्वपूर्ण है कि अपवाद की उपस्थिति में RAII-अनुरूप होना कितना महत्वपूर्ण है। लेकिन कच्चे पॉइंटर्स जो स्मृति का प्रबंधन / प्रबंधन नहीं करते हैं, उन्हें बहुत बुरा नहीं माना जाता है (यहां तक कि सटर और स्ट्रॉस्ट्रुप जैसे लोगों से भी) और कभी-कभी चीजों को इंगित करने के तरीके के रूप में बहुत व्यावहारिक (सूचकांकों के साथ जो चीजों को इंगित करते हैं)।
वे मानक कंटेनर पुनरावृत्तियों की तुलना में कम सुरक्षित नहीं हैं (कम से कम रिलीज़ में, अनुपस्थित जाँचकर्ता), जो यह पता नहीं लगाएगा कि आप अमान्य होने के बाद उन्हें हटाने का प्रयास करते हैं या नहीं। C ++ अभी भी unashamedly एक खतरनाक भाषा का एक सा है, मैं कहूँगा, जब तक कि इसका विशिष्ट उपयोग सब कुछ लपेटने और यहां तक कि गैर-स्वामित्व वाले कच्चे पॉइंटर्स को छिपाने के लिए नहीं चाहता। यह अपवादों के साथ लगभग महत्वपूर्ण है कि संसाधन RAII के अनुरूप हैं (जो आम तौर पर बिना रनटाइम लागत के आता है), लेकिन इसके अलावा यह जरूरी नहीं है कि लागत से बचने के पक्ष में उपयोग करने के लिए सबसे सुरक्षित भाषा होने की कोशिश की जाए जो एक डेवलपर स्पष्ट रूप से नहीं चाहता है विनिमय कुछ और के लिए। अनुशंसित उपयोग आपको झूलने वाले बिंदुओं और अमान्य पुनरावृत्तियों जैसी चीजों से बचाने की कोशिश नहीं कर रहा है, इसलिए बोलने के लिए (अन्यथा हम उपयोग करने के लिए प्रोत्साहित होंगेshared_ptr
पूरे स्थान पर, जो स्ट्रॉस्ट्रुप ने सख्ती से विरोध किया है)। यह आपको किसी चीज़ के संसाधन को ठीक से मुक्त / रिलीज़ / नष्ट / अनलॉक / साफ़ करने में विफल होने से बचाने की कोशिश कर रहा है throws
।