2009-05-01 19 views
222

से फेंकने अपवाद मैं कंस्ट्रक्टर्स से अपवाद फेंकने के बारे में एक सह कार्यकर्ता के साथ एक बहस हो रही हैं और सोचा था कि मैं कुछ प्रतिक्रिया करना चाहते हैं।कंस्ट्रक्टर्स

यह देखने के एक डिजाइन बिंदु से, निर्माताओं से अपवाद फेंक करना ठीक है?

चलें कहते हैं कि मैं एक कक्षा में एक POSIX म्युटेक्स लपेटकर रहा हूँ, यह कुछ इस तरह दिखेगा:

class Mutex { 
public: 
    Mutex() { 
    if (pthread_mutex_init(&mutex_, 0) != 0) { 
     throw MutexInitException(); 
    } 
    } 

    ~Mutex() { 
    pthread_mutex_destroy(&mutex_); 
    } 

    void lock() { 
    if (pthread_mutex_lock(&mutex_) != 0) { 
     throw MutexLockException(); 
    } 
    } 

    void unlock() { 
    if (pthread_mutex_unlock(&mutex_) != 0) { 
     throw MutexUnlockException(); 
    } 
    } 

private: 
    pthread_mutex_t mutex_; 
}; 

मेरा प्रश्न है, इस यह करने के लिए मानक तरीका है? क्योंकि अगर pthread mutex_init कॉल विफल रहता है mutex ऑब्जेक्ट अनुपयोगी है तो अपवाद फेंकने से यह सुनिश्चित होता है कि mutex नहीं बनाया जाएगा।

क्या मुझे म्यूटेक्स कक्षा के लिए सदस्य फ़ंक्शन init बनाना चाहिए और pthread mutex_init को कॉल करना चाहिए जिसके भीतर pthread mutex_init की वापसी के आधार पर एक बूल वापस कर देगा? इस तरह मुझे निम्न स्तर की वस्तु के लिए अपवादों का उपयोग करने की आवश्यकता नहीं है।

+0

संबंधित विषय पर एक और लिंक: http://www.writeulearn.com/exception-constructor/ –

+0

ठीक है, यह किसी अन्य समारोह से जितना ज्यादा है, उतना ही ठीक है, यह कहा जा रहा है कि आपको देखभाल के साथ फेंकना चाहिए किसी भी समारोह से। – g24l

+3

कुछ असंबंधित: क्यों नहीं अपने लॉक/अनलॉक विधियों को हटा रहा है, और सीधे म्यूटेक्स को कन्स्ट्रक्टर में लॉक करें और विनाशक में अनलॉक करें?इस तरह एक स्कोप में स्वचालित रूप से एक ऑटो वैरिएबल घोषित करना स्वचालित रूप से लॉक/अनलॉक, अपवादों, रिटर्न इत्यादि की देखभाल करने की कोई आवश्यकता नहीं है ... इसी तरह के कार्यान्वयन के लिए 'std :: lock_guard' देखें। –

उत्तर

207

हां, असफल कन्स्ट्रक्टर से अपवाद फेंकना यह करने का मानक तरीका है। अधिक जानकारी के लिए Handling a constructor that fails के बारे में इस FAQ को पढ़ें। एक init() विधि भी काम करेगी, लेकिन म्यूटेक्स के ऑब्जेक्ट को बनाने वाले सभी को याद रखना होगा कि init() को कॉल करना होगा। मुझे लगता है कि यह RAII सिद्धांत के खिलाफ चला जाता है।

+13

ज्यादातर स्थितियों में। Std :: fstream जैसी चीज़ों को मत भूलना। विफलता पर यह अभी भी एक वस्तु बनाता है, लेकिन क्योंकि हम हमेशा वस्तु की स्थिति का परीक्षण कर रहे हैं सामान्य रूप से यह अच्छी तरह से काम करता है। तो एक ऐसी वस्तु जिसमें प्राकृतिक उपयोग होता है जिसे सामान्य उपयोग के तहत परीक्षण किया जाता है, उसे फेंकने की आवश्यकता नहीं होती है। –

+1

@ विदर: मेरे सुझाए गए संपादन संख्या की समीक्षा के लिए धन्यवाद। 278978. मैं एक और, संपादन से संबंधित सवाल पूछता हूं? जिस टिप्पणी का यह टिप्पणी संलग्न है, उसका पुराना हाइपरलिंक है। इसे ठीक करने के लिए यूआरएल में "# faq-17.8" के साथ "# faq-17.2" को प्रतिस्थापित करने के लिए बिल्कुल एक वर्ण बदलना है। हालांकि, स्टैकओवरफ्लो के सॉफ़्टवेयर की आवश्यकता है कि मेरे जैसे कम प्रतिष्ठित उपयोगकर्ता द्वारा सबमिट किए गए संपादन में कम से कम छह वर्ण परिवर्तित हों। जाहिर है, टूटा हुआ लिंक तय करना चाहता है, और यह सिर्फ छः-चरित्र का फिक्स नहीं है। क्या आप जानते हैं कि मैं इसे कैसे ठीक कर सकता हूं? – thb

+4

वास्तव में, इस विशिष्ट मामले में, ध्यान दें कि उनके म्यूटेक्स विनाशक को कभी भी बुलाया नहीं जा सकता है, संभवतः पाथ्रेड म्यूटेक्स को लीक कर रहा है। इसका समाधान पाथ्रेड म्यूटेक्स के लिए एक स्मार्ट पॉइंटर का उपयोग करना है, बेहतर अभी तक बूस्ट म्यूटेक्स या std :: mutex का उपयोग करें, बेहतर विकल्प होने पर पुरानी कार्यात्मक-शैली ओएस संरचनाओं का उपयोग करने का कोई कारण नहीं है। –

0

हालांकि मैं सी ++ एक पेशेवर स्तर पर काम नहीं किया, मेरी राय में, यह ठीक कंस्ट्रक्टर्स से अपवाद फेंक है। मैं ऐसा करता हूं (यदि आवश्यक हो) .Net में। this और this लिंक देखें। यह आपकी रुचि का हो सकता है।

+10

.NET सी ++ नहीं है, न ही जावा है। फेंकने की व्यवस्था समान नहीं है और लागत अलग-अलग हैं। – g24l

30

एक अपवाद फेंकना कन्स्ट्रक्टर विफलता से निपटने का सबसे अच्छा तरीका है। आपको विशेष रूप से किसी वस्तु के अर्ध-निर्माण से बचने और फिर किसी प्रकार के ध्वज चर का परीक्षण करके निर्माण विफलता का पता लगाने के लिए अपनी कक्षा के उपयोगकर्ताओं पर भरोसा करना चाहिए।

एक संबंधित बिंदु पर, सच है कि आप म्युटेक्स त्रुटियों से निपटने के लिए कई अलग अलग प्रकार के अपवाद है कि मुझे थोड़ा चिंता। विरासत एक महान उपकरण है, लेकिन इसका अधिक उपयोग किया जा सकता है। इस मामले में मैं शायद एक एकल MutexError अपवाद पसंद करूंगा, संभवतः एक सूचनात्मक त्रुटि संदेश युक्त।

+0

मैं अपील की विरासत के बारे में दूसरा नील का मुद्दा दूंगा - एक म्यूटेक्स त्रुटि बेहतर विकल्प होने की संभावना है जब तक कि आप विशेष रूप से लॉक त्रुटि को अलग-अलग संभालना नहीं चाहते हैं। यदि आपके पास बहुत से अपवाद प्रकार हैं, तो उन्हें पकड़कर सभी थकाऊ और त्रुटि प्रवण हो सकते हैं। – markh44

+0

मैं मानता हूं कि एक प्रकार का म्यूटेक्स अपवाद पर्याप्त है। और यह त्रुटि को अधिक अंतर्ज्ञानी बनाने में भी त्रुटि देगा। – lkristjansen

85

आप एक निर्माता से एक अपवाद फेंक, ध्यान रखें कि आप समारोह ट्राई/कैच सिंटैक्स का उपयोग करने के लिए यदि आप एक निर्माता प्रारंभकर्ता सूची में उस अपवाद को पकड़ने के लिए की जरूरत की जरूरत है रखने करते हैं।

उदा।

func::func() : foo() 
{ 
    try {...} 
    catch (...) // will NOT catch exceptions thrown from foo constructor 
    { ... } 
} 

बनाम

func::func() 
    try : foo() {...} 
    catch (...) // will catch exceptions thrown from foo constructor 
    { ... } 
+25

यह ध्यान दिया जाना चाहिए कि उप ऑब्जेक्ट के निर्माण से उठाए गए अपवादों को दबाया नहीं जा सकता है: http://www.gotw.ca/gotw/066.htm –

10

यह अपने निर्माता से फेंक करने के लिए ठीक है, लेकिन आप और सुनिश्चित करें कि मुख्य प्रारंभ होने के बाद अपने वस्तु का निर्माण किया है बनाना चाहिए इससे पहले कि यह खत्म:

class A 
{ 
public: 
    A() { 
    throw int(); 
    } 
}; 

A a;  // Implementation defined behaviour if exception is thrown (15.3/13) 

int main() 
{ 
    try 
    { 
    // Exception for 'a' not caught here. 
    } 
    catch (int) 
    { 
    } 
} 
2

केवल समय आप नहीं कंस्ट्रक्टर्स से अपवाद फेंक है अगर अपनी परियोजना एक है अपवादों का उपयोग करने के खिलाफ नियम (उदाहरण के लिए, Google अपवाद पसंद नहीं है)। उस स्थिति में, आप कहीं भी कहीं भी अपने कन्स्ट्रक्टर में अपवादों का उपयोग नहीं करना चाहते हैं, और इसके बजाय आपको किसी प्रकार की एक इनिट विधि रखना होगा।

+0

आपको Google दिशानिर्देशों पर लंबी चर्चा में रुचि हो सकती है http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/d7b0a5c663467471/2e5707c44726925a?lnk=gst&q=google#2e5707c44726925a –

+4

दिलचस्प चर्चा। मेरी व्यक्तिगत राय यह है कि आपको केवल अपवादों का उपयोग करना चाहिए जब आप वास्तव में प्रोग्राम का त्रुटि प्रबंधन संरचना तैयार करते हैं ताकि उनका लाभ उठाया जा सके। यदि आप कोड लिखने के बाद त्रुटि प्रबंधन करने का प्रयास करते हैं, या उन कार्यक्रमों में अपवादों को अपनाने का प्रयास करें जो उनके लिए नहीं लिखे गए थे, तो यह सिर्फ हर किसी को कोशिश करने/पकड़ने (अपवादों के फायदे को खत्म करने) या कार्यक्रमों को दुर्घटनाग्रस्त करने के लिए नेतृत्व करने जा रहा है कम से कम छोटी त्रुटि। मैं हर दिन दोनों के साथ सौदा करता हूं और मुझे यह पसंद नहीं है। –

3

यदि आपकी परियोजना आम तौर पर अच्छे डेटा से खराब डेटा को अलग करने के अपवादों पर निर्भर करती है, तो कन्स्ट्रक्टर से अपवाद फेंकना बेहतर समाधान नहीं है। अगर अपवाद नहीं फेंक दिया जाता है, तो ऑब्जेक्ट ज़ोंबी राज्य में शुरू किया जाता है। इस तरह के ऑब्जेक्ट को ध्वज का पर्दाफाश करने की ज़रूरत है जो कहती है कि वस्तु सही है या नहीं। इस तरह कुछ:

class Scaler 
{ 
    public: 
     Scaler(double factor) 
     { 
      if (factor == 0) 
      { 
       _state = 0; 
      } 
      else 
      { 
       _state = 1; 
       _factor = factor; 
      } 
     } 

     double ScaleMe(double value) 
     { 
      if (!_state) 
       throw "Invalid object state."; 
      return value/_factor; 
     } 

     int IsValid() 
     { 
      return _status; 
     } 

    private: 
     double _factor; 
     int _state; 

} 

इस दृष्टिकोण के साथ समस्या कॉलर पक्ष पर है। वास्तव में ऑब्जेक्ट का उपयोग करने से पहले वर्ग के प्रत्येक उपयोगकर्ता को ऐसा करना होगा। यह बग के लिए एक कॉल है - जारी रखने से पहले एक शर्त का परीक्षण करने के लिए भूलने से कुछ भी आसान नहीं है।

कन्स्ट्रक्टर से अपवाद फेंकने के मामले में, ऑब्जेक्ट जो संरचना बनाता है उसे तुरंत समस्याओं का ख्याल रखना चाहिए। स्ट्रीम के नीचे ऑब्जेक्ट उपभोक्ता यह मानने के लिए स्वतंत्र हैं कि ऑब्जेक्ट केवल 100% परिचालन है जो उन्होंने प्राप्त किया है।

यह चर्चा कई दिशाओं में जारी रख सकती है।

उदाहरण के लिए, सत्यापन के मामले के रूप में अपवादों का उपयोग करना एक बुरा अभ्यास है। ऐसा करने का एक तरीका कारखाना वर्ग के संयोजन के साथ एक कोशिश पैटर्न है। आप पहले से ही कारखानों का उपयोग कर रहे हैं, तो दो तरीकों लिखें:

class ScalerFactory 
{ 
    public: 
     Scaler CreateScaler(double factor) { ... } 
     int TryCreateScaler(double factor, Scaler **scaler) { ... }; 
} 
यह समाधान आपको यथा-स्थान स्थिति झंडा प्राप्त कर सकते हैं, कारखाने विधि की वापसी मान के रूप में साथ

, कभी बुरा डेटा के साथ निर्माता में प्रवेश के बिना ।

दूसरी बात यह है कि यदि आप स्वचालित परीक्षण के साथ कोड को कवर कर रहे हैं। उस मामले में कोड का हर टुकड़ा जो ऑब्जेक्ट का उपयोग करता है जो अपवाद नहीं फेंकता है उसे एक अतिरिक्त परीक्षण के साथ कवर किया जाना चाहिए - चाहे यह सही तरीके से कार्य करता है जब IsValid() विधि गलत होती है। यह काफी अच्छी तरह से बताता है कि ज़ोंबी राज्य में वस्तुओं को शुरू करना एक बुरा विचार है।

3

अलावा तथ्य यह है कि आप अपने विशिष्ट मामले में निर्माता से फेंक क्योंकि pthread_mutex_lock actually returns an EINVAL if your mutex has not been initialized और आप lock करने के लिए कॉल के बाद फेंक कर सकते हैं के रूप में std::mutex में किया जाता है की जरूरत नहीं है से:

void 
lock() 
{ 
    int __e = __gthread_mutex_lock(&_M_mutex); 

    // EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may) 
    if (__e) 
__throw_system_error(__e); 
} 

तो में कंस्ट्रक्टर्स से सामान्य फेंक ठीक निर्माण के दौरान अधिग्रहण त्रुटियों के लिए है, और आरए II (संसाधन-प्राप्ति-है-प्रारंभ) प्रोग्रामिंग प्रतिमान के अनुपालन में।

चेक इन बयानों पर इस example on RAII

void write_to_file (const std::string & message) { 
    // mutex to protect file access (shared across threads) 
    static std::mutex mutex; 

    // lock mutex before accessing file 
    std::lock_guard<std::mutex> lock(mutex); 

    // try to open file 
    std::ofstream file("example.txt"); 
    if (!file.is_open()) 
     throw std::runtime_error("unable to open file"); 

    // write message to file 
    file << message << std::endl; 

    // file will be closed 1st when leaving scope (regardless of exception) 
    // mutex will be unlocked 2nd (from lock destructor) when leaving 
    // scope (regardless of exception) 
} 

फोकस:

  1. static std::mutex mutex
  2. std::lock_guard<std::mutex> lock(mutex);
  3. std::ofstream file("example.txt");

पहला कथन आरएआईआई और noexcept है।में (2) यह स्पष्ट है कि lock_guard पर RAII लागू किया गया है और यह वास्तव में throw कर सकता है, जबकि (3) ofstream में RAII नहीं लगता है, क्योंकि ऑब्जेक्ट्स को is_open() पर कॉल करके चेक किया जाना चाहिए जो failbit ध्वज को चेक करता है।

पहली नज़र में ऐसा लगता है कि यह ओ पी कार्यान्वयन के विपरीत यह मानक तरीका और पहले मामले std::mutex में प्रारंभ में फेंक नहीं है क्या पर दुविधा में पड़ा हुआ है, * *। दूसरे मामले में यह std::mutex::lock से जो भी फेंक दिया गया है उसे फेंक देगा, और तीसरे में कोई फेंक नहीं होगा।

सूचना मतभेद:

(1) स्थिर घोषित किया जा सकता है, और वास्तव में एक सदस्य चर (2) वास्तव में एक सदस्य चर के रूप में घोषित करने की कभी नहीं की उम्मीद होगी के रूप में घोषित किया जाएगा (3) है सदस्य चर के रूप में घोषित होने की उम्मीद है, और अंतर्निहित संसाधन हमेशा उपलब्ध नहीं हो सकता है।

ये सभी रूप RAII हैं; इसे हल करने के लिए, किसी को RAII का विश्लेषण करना होगा।

  • संसाधन: अपनी वस्तु
  • अधिग्रहण (आवंटन): यदि आप बनाया जा रहा वस्तु
  • प्रारंभ: अपने ऑब्जेक्ट को उसके अपरिवर्तनीय राज्य में है

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

इसलिए, आपकी समस्या आपके शुरुआती राज्य को परिभाषित करने के लिए उबलती है। यदि आपके मामले में आपकी प्रारंभिक स्थिति है mutex को प्रारंभ किया जाना चाहिए तो आपको कन्स्ट्रक्टर से फेंकना चाहिए। इसके विपरीत यह ठीक है कि शुरूआत न करें (जैसा कि std::mutex में किया गया है), और म्यूटेक्स के रूप में आपके इनविरिएंट स्टेट को परिभाषित करें। किसी भी हाल में अपरिवर्तनीय उसके सदस्य वस्तु के राज्य द्वारा जरूरी compromized नहीं है, Mutex सार्वजनिक तरीकों Mutex::lock() और Mutex::unlock() के माध्यम से locked और unlocked के बीच mutex_ वस्तु mutates के बाद से।

class Mutex { 
private: 
    int e; 
    pthread_mutex_t mutex_; 

public: 
    Mutex(): e(0) { 
    e = pthread_mutex_init(&mutex_); 
    } 

    void lock() { 

    e = pthread_mutex_lock(&mutex_); 
    if(e == EINVAL) 
    { 
     throw MutexInitException(); 
    } 
    else (e) { 
     throw MutexLockException(); 
    } 
    } 

    // ... the rest of your class 
}; 
8
#include <iostream> 

class bar 
{ 
public: 
    bar() 
    { 
    std::cout << "bar() called" << std::endl; 
    } 

    ~bar() 
    { 
    std::cout << "~bar() called" << std::endl; 

    } 
}; 
class foo 
{ 
public: 
    foo() 
    : b(new bar()) 
    { 
    std::cout << "foo() called" << std::endl; 
    throw "throw something"; 
    } 

    ~foo() 
    { 
    delete b; 
    std::cout << "~foo() called" << std::endl; 
    } 

private: 
    bar *b; 
}; 


int main(void) 
{ 
    try { 
    std::cout << "heap: new foo" << std::endl; 
    foo *f = new foo(); 
    } catch (const char *e) { 
    std::cout << "heap exception: " << e << std::endl; 
    } 

    try { 
    std::cout << "stack: foo" << std::endl; 
    foo f; 
    } catch (const char *e) { 
    std::cout << "stack exception: " << e << std::endl; 
    } 

    return 0; 
} 

उत्पादन:

heap: new foo 
bar() called 
foo() called 
heap exception: throw something 
stack: foo 
bar() called 
foo() called 
stack exception: throw something 

विनाशकर्ता इसलिए यदि एक अपवाद एक निर्माता में फेंक दिया जाने की जरूरत है, कहा जाता है नहीं कर रहे हैं, सामान का एक बहुत (जैसे साफ?) करने के लिए ।

+0

बहुत अच्छा बिंदु। मुझे हैरान है कि इस तरह के रिसाव को कोई अन्य जवाब नहीं है। – Carlton

+3

आपको एक std :: unique_ptr या इसी तरह का उपयोग करना चाहिए। सदस्यों के विनाशक को कहा जाता है कि निर्माण के दौरान एक अपवाद फेंक दिया गया है, लेकिन सादे पॉइंटर्स के पास कोई नहीं है। 'Bar * b' को 'std :: unique_ptr b' के साथ बदलें (आपको' हटाएं बी 'हटा देना होगा और' 'शीर्षलेख जोड़ें), और फिर से चलाएं। – cbuchart

+1

यह व्यवहार काफी समझदार है। अगर निर्माता विफल रहा है (सफलतापूर्वक पूरा नहीं हुआ) तो विनाशक को क्यों बुलाया जाना चाहिए? इसमें साफ करने के लिए कुछ भी नहीं है और यदि उन वस्तुओं को साफ करने की कोशिश की गई है जिन्हें ठीक से तुरंत नहीं किया गया है (कुछ पॉइंटर्स सोचें), तो इससे अनावश्यक रूप से बहुत अधिक समस्याएं पैदा हो जाएंगी। – zar

संबंधित मुद्दे