आपका प्रश्न यह दावा करता है कि, "अपवाद-सुरक्षित कोड लिखना बहुत कठिन है"। मैं पहले आपके प्रश्नों का उत्तर दूंगा, और फिर, उनके पीछे छिपे प्रश्न का उत्तर दूंगा।
सवालों का जवाब दे
क्या आप वास्तव में अपवाद सुरक्षित कोड लिखते हैं?
बेशक मैं।
यही कारण है कि जावा ने C ++ प्रोग्रामर (RAII शब्दार्थ की कमी) के रूप में मुझसे अपनी अपील को बहुत खो दिया है, लेकिन मैं पचा रहा हूं: यह C ++ प्रश्न है।
यह वास्तव में आवश्यक है, जब आपको एसटीएल या बूस्ट कोड के साथ काम करने की आवश्यकता होती है। उदाहरण के लिए, C ++ थ्रेड्स ( boost::thread
या std::thread
) इनायत से बाहर निकलने के लिए एक अपवाद फेंक देंगे।
क्या आप सुनिश्चित हैं कि आपका अंतिम "प्रोडक्शन रेडी" कोड अपवाद सुरक्षित है?
क्या आप यह भी सुनिश्चित कर सकते हैं, कि यह क्या है?
अपवाद-सुरक्षित कोड लिखना बग-मुक्त कोड लिखने जैसा है।
आप 100% सुनिश्चित नहीं हो सकते कि आपका कोड अपवाद सुरक्षित है। लेकिन फिर, आप इसके लिए प्रयास करते हैं, अच्छी तरह से ज्ञात पैटर्न का उपयोग करते हैं, और प्रसिद्ध विरोधी पैटर्न से बचते हैं।
क्या आप जानते हैं और / या वास्तव में काम करने वाले विकल्पों का उपयोग करते हैं?
C ++ में कोई व्यवहार्य विकल्प नहीं हैं (यानी आपको C पर वापस लौटना होगा और C ++ लाइब्रेरी से बचना होगा, साथ ही बाहरी आश्चर्य जैसे विंडोज SEH)।
अपवाद सुरक्षित कोड लिखना
अपवाद सुरक्षित कोड लिखने के लिए, आपको पहले पता होना चाहिए कि प्रत्येक निर्देश आपके द्वारा लिखे गए अपवाद सुरक्षा के किस स्तर पर है।
उदाहरण के लिए, एक new
एक अपवाद फेंक सकते हैं, लेकिन बताए एक अंतर्निहित (एक पूर्णांक है, या एक सूचक जैसे) असफल नहीं होंगे। एक स्वैप कभी असफल नहीं होगा (कभी फेंकने वाला स्वैप न लिखें), std::list::push_back
फेंक सकता है ...
अपवाद की गारंटी
समझने की पहली बात यह है कि आपको अपने सभी कार्यों द्वारा दी गई अपवाद गारंटी का मूल्यांकन करने में सक्षम होना चाहिए:
- कोई नहीं : आपके कोड को कभी भी प्रस्ताव नहीं देना चाहिए। यह कोड सब कुछ लीक कर देगा, और पहले फेंके गए अपवाद पर टूट जाएगा।
- मूल : यह गारंटी है कि आपको बहुत कम से कम प्रस्ताव देना चाहिए, अर्थात यदि कोई अपवाद फेंका गया है, तो कोई संसाधन लीक नहीं हुए हैं, और सभी वस्तुएं अभी भी पूरी हैं
- मजबूत : प्रसंस्करण या तो सफल होगा, या एक अपवाद को फेंक देगा, लेकिन अगर यह फेंकता है, तो डेटा उसी स्थिति में होगा जैसे कि प्रसंस्करण बिल्कुल भी शुरू नहीं हुआ था (यह C ++ को एक लेन-देन की शक्ति देता है)
- nothrow / nofail : प्रसंस्करण सफल होगा।
कोड का उदाहरण
निम्न कोड सही C ++ की तरह लगता है, लेकिन सच में, "कोई नहीं" गारंटी प्रदान करता है, और इस प्रकार, यह सही नहीं है:
void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
X * x = new X() ; // 2. basic : can throw with new and X constructor
t.list.push_back(x) ; // 3. strong : can throw
x->doSomethingThatCanThrow() ; // 4. basic : can throw
}
मैं इस तरह के विश्लेषण के साथ अपने सभी कोड लिखता हूं।
प्रदान की गई न्यूनतम गारंटी बुनियादी है, लेकिन फिर, प्रत्येक निर्देश का क्रम पूरे फ़ंक्शन को "कोई नहीं" बनाता है, क्योंकि यदि 3. फेंकता है, तो एक्स लीक हो जाएगा।
पहली बात यह है कि फ़ंक्शन को "बेसिक" बनाना होगा, जो कि एक स्मार्ट पॉइंटर में x डाल रहा है जब तक कि यह सुरक्षित रूप से सूची के स्वामित्व में न हो:
void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
std::auto_ptr<X> x(new X()) ; // 2. basic : can throw with new and X constructor
X * px = x.get() ; // 2'. nothrow/nofail
t.list.push_back(px) ; // 3. strong : can throw
x.release() ; // 3'. nothrow/nofail
px->doSomethingThatCanThrow() ; // 4. basic : can throw
}
अब, हमारा कोड एक "बुनियादी" गारंटी प्रदान करता है। कुछ भी लीक नहीं होगा, और सभी ऑब्जेक्ट एक सही स्थिति में होंगे। लेकिन हम और अधिक, यानी मजबूत गारंटी दे सकते हैं। यह वह जगह है जहां यह कर सकते हैं महंगा हो जाते हैं, और ऐसा क्यों है न सब सी ++ कोड मजबूत है। चलो यह कोशिश करते हैं:
void doSomething(T & t)
{
// we create "x"
std::auto_ptr<X> x(new X()) ; // 1. basic : can throw with new and X constructor
X * px = x.get() ; // 2. nothrow/nofail
px->doSomethingThatCanThrow() ; // 3. basic : can throw
// we copy the original container to avoid changing it
T t2(t) ; // 4. strong : can throw with T copy-constructor
// we put "x" in the copied container
t2.list.push_back(px) ; // 5. strong : can throw
x.release() ; // 6. nothrow/nofail
if(std::numeric_limits<int>::max() > t2.integer) // 7. nothrow/nofail
t2.integer += 1 ; // 7'. nothrow/nofail
// we swap both containers
t.swap(t2) ; // 8. nothrow/nofail
}
हमने संचालन को फिर से आदेश दिया, पहले X
इसके सही मूल्य पर निर्माण और सेटिंग । यदि कोई ऑपरेशन विफल हो जाता है, तो t
संशोधित नहीं किया जाता है, इसलिए, ऑपरेशन 1 से 3 को "मजबूत" माना जा सकता है: यदि कुछ फेंकता है, t
तो संशोधित नहीं किया गया है, और X
रिसाव नहीं होगा क्योंकि यह स्मार्ट पॉइंटर के स्वामित्व में है।
फिर, हम एक प्रतिलिपि बनाने t2
के t
7. करने के लिए, और ऑपरेशन 4 से इस प्रति पर काम कुछ फेंकता है, तो t2
संशोधित किया गया है, लेकिन फिर, t
अभी भी मूल है। हम अभी भी मजबूत गारंटी प्रदान करते हैं।
फिर, हम स्वैप t
और t2
। स्वैप ऑपरेशन C ++ में नहीं होना चाहिए, तो चलिए आशा करते हैं कि आपने जो स्वैप लिखा है T
वह nothrow है (यदि ऐसा नहीं है, तो इसे फिर से लिखें क्योंकि यह nothrow है)।
इसलिए, यदि हम फ़ंक्शन के अंत तक पहुंचते हैं, तो सब कुछ सफल हुआ (रिटर्न प्रकार की कोई आवश्यकता नहीं) और t
इसका अपवाद मूल्य है। यदि यह विफल रहता है, तो t
अभी भी इसका मूल मूल्य है।
अब, मजबूत गारंटी की पेशकश करना काफी महंगा हो सकता है, इसलिए अपने सभी कोड को मजबूत गारंटी की पेशकश करने का प्रयास न करें, लेकिन यदि आप इसे बिना लागत के कर सकते हैं (और C ++ inlining और अन्य अनुकूलन सभी कोड को महंगा बना सकते हैं) , तो यह करो। फ़ंक्शन उपयोगकर्ता इसके लिए आपको धन्यवाद देगा।
निष्कर्ष
अपवाद-सुरक्षित कोड लिखने की कुछ आदतें होती हैं। आपको उपयोग किए जाने वाले प्रत्येक निर्देश द्वारा दी गई गारंटी का मूल्यांकन करने की आवश्यकता होगी, और फिर, आपको निर्देशों की एक सूची द्वारा दी गई गारंटी का मूल्यांकन करने की आवश्यकता होगी।
बेशक, C ++ कंपाइलर गारंटी को वापस नहीं करेगा (मेरे कोड में, मैं गारंटी को @warning doxygen टैग के रूप में प्रस्तुत करता हूं), जो थोड़े दुखद है, लेकिन यह आपको अपवाद-सुरक्षित कोड लिखने की कोशिश करने से नहीं रोकना चाहिए।
सामान्य विफलता बनाम बग
एक प्रोग्रामर कैसे गारंटी दे सकता है कि कोई भी विफल कार्य हमेशा सफल नहीं होगा? आखिरकार, फ़ंक्शन बग हो सकता है।
यह सच है। बग-मुक्त कोड द्वारा अपवाद गारंटी की पेशकश की जानी चाहिए। लेकिन फिर, किसी भी भाषा में, फ़ंक्शन को दबाने से फ़ंक्शन बग मुक्त हो जाता है। बग होने की संभावना के खिलाफ कोई भी संन्यासी कोड खुद को बचाता नहीं है। सबसे अच्छा कोड आप लिख सकते हैं, और फिर, यह बग रहित होने की गारंटी के साथ गारंटी प्रदान करें। और अगर कोई बग है, तो उसे ठीक करें।
अपवाद असाधारण प्रसंस्करण विफलता के लिए हैं, कोड बग के लिए नहीं।
आखरी श्ब्द
अब, सवाल "क्या यह इसके लायक है?"।
निश्चित रूप से यह है। एक "nothrow / no-fail" फ़ंक्शन के होने के कारण यह जानना कि फ़ंक्शन विफल नहीं होगा, एक बड़ा वरदान है। एक "मजबूत" फ़ंक्शन के लिए भी यही कहा जा सकता है, जो आपको डेटाबेस, जैसे कमिट / रोलबैक सुविधाओं के साथ ट्रांजेक्शनल शब्दार्थ के साथ कोड लिखने में सक्षम बनाता है, कमिट कोड का सामान्य निष्पादन है, अपवाद को रोलबैक होने के लिए फेंक देता है।
फिर, "मूल" आपको प्रदान की जाने वाली बहुत कम गारंटी है। C ++ एक बहुत ही मजबूत भाषा है, इसके स्कैप्स के साथ, आपको किसी भी संसाधन लीक से बचने के लिए सक्षम बनाता है (कुछ कचरा संग्रहकर्ता को डेटाबेस, कनेक्शन या फ़ाइल हैंडल के लिए ऑफ़र करना मुश्किल होगा)।
तो, जहाँ तक मैं इसे देखना रूप में, यह है इसके लायक।
2010-01-29 संपादित करें: गैर-फेंकने वाली स्वैप के बारे में
nobar ने एक टिप्पणी की जो मुझे विश्वास है, काफी प्रासंगिक है, क्योंकि यह "आप अपवाद सुरक्षित कोड कैसे लिखते हैं" का एक हिस्सा है:
- [मुझे] एक स्वैप कभी असफल नहीं होगा (एक फेंक स्वैप भी नहीं लिखें)
- [nobar] यह कस्टम लिखित
swap()
कार्यों के लिए एक अच्छी सिफारिश है। हालांकि, यह ध्यान दिया जाना चाहिए std::swap()
कि आंतरिक रूप से उपयोग किए जाने वाले संचालन के आधार पर यह विफल हो सकता है
डिफ़ॉल्ट std::swap
प्रतियां और कार्य करेगा, जो कुछ वस्तुओं के लिए फेंक सकता है। इस प्रकार, डिफ़ॉल्ट स्वैप फेंक सकता है, या तो आपकी कक्षाओं के लिए या एसटीएल कक्षाओं के लिए भी उपयोग किया जाता है। जहां तक C ++ मानक का सवाल है, स्वैप ऑपरेशन के लिए vector
, deque
और list
नहीं फेंकेंगे, जबकि यह इस बात के लिए हो सकता है map
कि तुलना फ़ेंक्टर कॉपी निर्माण पर फेंक सकता है (देखें C ++ प्रोग्रामिंग लैंग्वेज, स्पेशल एडिशन, परिशिष्ट E, E.4.3 .Swap )।
सदिश की अदला-बदली के विजुअल C ++ 2008 के कार्यान्वयन को देखते हुए, यदि दोनों वैक्टर में एक ही एलोकेटर (यानी सामान्य स्थिति) है, तो वेक्टर की अदला-बदली नहीं होगी, लेकिन अगर उनके पास अलग-अलग एलोकेटर हैं तो वे प्रतियां बना देंगे। और इस प्रकार, मुझे लगता है कि यह इस अंतिम मामले में फेंक सकता है।
इसलिए, मूल पाठ अभी भी धारण करता है: कभी भी फेंकने वाली स्वैप न लिखें, लेकिन नॉबर की टिप्पणी को याद रखना चाहिए: सुनिश्चित करें कि जिन वस्तुओं को आप स्वैप कर रहे हैं, वे एक गैर-फेंकने वाली स्वैप हैं।
2011-11-06 को संपादित करें: दिलचस्प लेख
डेव अब्राहम , जिन्होंने हमें बुनियादी / मजबूत / नोटरी गारंटी दी , एक लेख में एसटीएल अपवाद बनाने के बारे में अपने अनुभव का वर्णन किया:
http://www.boost.org/community/exception_safety.html
7 वें बिंदु को देखें (अपवाद-सुरक्षा के लिए स्वचालित परीक्षण), जहां वह यह सुनिश्चित करने के लिए स्वचालित इकाई परीक्षण पर निर्भर है कि हर मामले का परीक्षण किया जाए। मुझे लगता है कि यह भाग प्रश्न लेखक के " क्या आप भी सुनिश्चित कर सकते हैं, कि यह है? " का एक उत्कृष्ट उत्तर है ।
संपादित करें 2013-05-31: डायनाडार से टिप्पणी
t.integer += 1;
गारंटी के बिना है कि अतिप्रवाह सुरक्षित अपवाद नहीं होगा, और वास्तव में तकनीकी रूप से यूबी को लागू कर सकता है! (हस्ताक्षरित अतिप्रवाह UB: C ++ 11 5/4 है "यदि किसी अभिव्यक्ति के मूल्यांकन के दौरान, परिणाम गणितीय रूप से परिभाषित नहीं है या अपने प्रकार के लिए प्रतिनिधित्व योग्य मूल्यों की सीमा में नहीं है, तो व्यवहार अपरिभाषित है।") नोट अहस्ताक्षरित है। पूर्णांक अतिप्रवाह नहीं करते हैं, लेकिन उनकी गणना एक समतुल्य श्रेणी मोडुलो 2 ^ # बिट्स में करते हैं।
डायनाडार निम्नलिखित पंक्ति का उल्लेख कर रहा है, जिसमें वास्तव में अपरिभाषित व्यवहार है।
t.integer += 1 ; // 1. nothrow/nofail
यहाँ समाधान यह सत्यापित करने के लिए है कि पूर्णांक पहले से ही अधिकतम मूल्य पर है (उपयोग करते हुए std::numeric_limits<T>::max()
) इसके अतिरिक्त।
मेरी त्रुटि "सामान्य विफलता बनाम बग" अनुभाग में जाएगी, जो कि एक बग है। यह तर्क को अमान्य नहीं करता है, और इसका मतलब यह नहीं है कि अपवाद-सुरक्षित कोड बेकार है क्योंकि इसे प्राप्त करना असंभव है। आप कंप्यूटर को बंद करने, या कंपाइलर बग्स, या यहाँ तक कि अपने बग्स, या अन्य त्रुटियों के खिलाफ खुद की रक्षा नहीं कर सकते। आप पूर्णता प्राप्त नहीं कर सकते हैं, लेकिन आप जितना संभव हो उतना निकट पाने की कोशिश कर सकते हैं।
मैंने डायनाडार की टिप्पणी को ध्यान में रखते हुए कोड को सही किया।