2012-03-19 10 views
7

मैं वर्तमान में हमारे कोड आधार में सभी बुरे प्रथाओं को ढूंढने और अपमानजनक कोड को ठीक करने के लिए अपने सहयोगियों को मनाने के लिए प्रभारी हूं। मेरी spelunking के दौरान, मैंने देखा है कि कई लोगों को यहाँ निम्नलिखित पैटर्न का उपयोग करें:रचनाकारों की बजाय "प्रारंभिक()" विधि के खिलाफ तर्क

class Foo 
{ 
    public: 
    Foo() { /* Do nothing here */ } 
    bool initialize() { /* Do all the initialization stuff and return true on success. */ } 
    ~Foo() { /* Do all the cleanup */ } 
}; 

अब मैं गलत हो सकता है, लेकिन यह मेरे लिए initialize() विधि बात भयंकर है। मेरा मानना ​​है कि यह रचनाकारों के पूरे उद्देश्य को रद्द कर देता है।

जब मैं अपने सहकर्मियों से पूछना क्यों इस डिजाइन निर्णय लिया गया था, वे हमेशा का जवाब वे कोई विकल्प नहीं है कि क्योंकि तुम फेंक बिना एक निर्माता से बाहर निकलने नहीं कर सकते हैं (मैं वे यह मान फेंक हमेशा बुरा है लगता है)।

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

धन्यवाद।

+1

FTR, इस दो चरण प्रारंभ के रूप में जाना जाता है। –

+1

यहां एक स्पष्ट सवाल है: यदि निर्माण विफल रहता है, * आपके सहकर्मी आधे रास्ते से निर्मित वस्तु * के साथ क्या करना चाहते हैं? यह किसी काम का नहीं। यह निर्माण – jalf

+0

@jalf पर एक अपवाद फेंक दिया गया है: वास्तव में अच्छा बिंदु। – ereOn

उत्तर

8

दोनों चरण (कन्स्ट्रक्टर) प्रारंभिकरण और दो चरण (एक इनिट विधि के साथ) प्रारंभिकरण उपयोगी पैटर्न हैं। व्यक्तिगत रूप से मुझे लगता है कि या तो छोड़कर कोई गलती है, हालांकि अपने सम्मेलनों अपवाद के उपयोग को प्रतिबंधित करता है, तो पूरी तरह से तो आप निर्माताओं के लिए ही चरण initialisation कि असफल हो सकता है प्रतिबंधित करते हैं।

सामान्य तौर पर मैं ही चरण initialisation पसंद करते हैं क्योंकि यह मतलब है कि आपका वस्तुओं मजबूत अपरिवर्तनशीलताओं हो सकता है।मैं केवल दो चरण प्रारंभिकरण का उपयोग करता हूं जब मैं इसे किसी वस्तु के लिए अर्थपूर्ण या उपयोगी मानता हूं जो "अनियंत्रित" स्थिति में मौजूद हो सकता है।

दो चरण प्रारंभिकरण के साथ यह आपके ऑब्जेक्ट के लिए एक प्रारंभिक स्थिति में होना मान्य है - इसलिए ऑब्जेक्ट के साथ काम करने वाली हर विधि को तथ्य से परिचित और सही ढंग से संभालने की आवश्यकता है कि यह एक प्रारंभिक स्थिति में हो सकता है। यह पॉइंटर्स के साथ काम करने के समान है, जहां यह मानने के लिए खराब रूप है कि एक सूचक न्यूल नहीं है। इसके विपरीत, यदि आप अपने कन्स्ट्रक्टर में अपनी सभी प्रारंभिकता करते हैं और अपवादों के साथ असफल होते हैं, तो आप इनवेंटर्स की सूची में 'ऑब्जेक्ट हमेशा प्रारंभ किया जाता है' जोड़ सकते हैं, और इसलिए यह ऑब्जेक्ट की स्थिति के बारे में धारणाएं आसान और सुरक्षित हो जाता है।

2

इसे आमतौर पर दो चरण या मल्टीफेस प्रारंभिक के रूप में जाना जाता है और यह विशेष रूप से खराब होता है क्योंकि एक बार कन्स्ट्रक्टर कॉल सफलतापूर्वक समाप्त हो जाने के बाद, आपके पास ऑब्जेक्ट का उपयोग करने के लिए तैयार होना चाहिए, इस मामले में आपके पास तैयार करने के लिए तैयार नहीं होगा, वस्तु का प्रयोग करें।

मैं मदद नहीं कर सकता लेकिन निम्नलिखित पर अधिक तनाव देता हूं:
असफलता के मामले में कन्स्ट्रक्टर से अपवाद फेंकना ऑब्जेक्ट निर्माण विफलताओं को संभालने का सबसे अच्छा और एकमात्र संक्षिप्त तरीका है।

+0

देखें यह स्वीकार्य उत्तर होना चाहिए। – maxschlepzig

0

यह इस मामले पर निर्भर करता है।

यदि कोई तर्ककर्ता कुछ तर्कों के कारण विफल हो सकता है, तो एक अपवाद फेंक दिया जाना चाहिए। लेकिन, ज़ाहिर है, आपको रचनाकारों से अपवाद फेंकने पर खुद को दस्तावेज करने की जरूरत है।

यदि Foo में ऑब्जेक्ट्स हैं, तो उन्हें एक बार कन्स्ट्रक्टर में दो बार शुरू किया जाएगा, एक बार initialize विधि में, इसलिए यह एक कमी है।

आईएमओ, सबसे बड़ी कमी यह है कि आपको initialize पर कॉल करना याद रखना होगा। ऑब्जेक्ट बनाने का क्या मतलब है यदि यह अमान्य है?

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

यदि, हालांकि, वे कुछ प्रकार के आलसी प्रारंभिकरण चाहते हैं, यह मान्य है।

0

यह दर्द है, लेकिन यदि आप कन्स्ट्रक्टर से अपवाद फेंकना नहीं चाहते हैं तो आपके पास कोई अन्य विकल्प नहीं है। एक और विकल्प भी है, उतना ही दर्दनाक: कन्स्ट्रक्टर में सभी प्रारंभिक कार्य करें और फिर आपको यह जांचना होगा कि ऑब्जेक्ट का निर्माण सफलतापूर्वक किया गया है (उदा। रूपांतरण ऑपरेटर बूल या IsOK विधि)। जीवन कठिन है, ..... और फिर आप मर जाते हैं :(

+0

लेकिन यदि किसी ऑब्जेक्ट को प्रारंभ नहीं किया जा सका (और इस प्रकार, निर्माण नहीं किया जा सका), तो इसका क्या उपयोग होता है? मेरे लिए 'आईएसओके' विधि की बात समान रूप से गलत है। – ereOn

+0

विचार यह है कि कन्स्ट्रक्टर में आप केवल सभी सदस्यों को प्रारंभिक मानों पर सेट करते हैं (इसलिए उनमें से सभी परिभाषित राज्य में हैं), फिर आप 'प्रारंभिक' आह्वान करते हैं जो संभावित रूप से अपवाद फेंक सकता है। कन्स्ट्रक्टर के ठीक बाद 'आईएसओके' (या ऑपरेटर बूल) झूठा लौटाता है। ऑब्जेक्ट को हमेशा प्रारंभिक (ज्ञात) मानों में प्रारंभ किया जा सकता है। मैं बहस नहीं कर रहा हूं कि यह एक अच्छा विचार है, कभी-कभी यह केवल एक ही विकल्प है। बीटीडब्लू: यदि आप प्रारंभिक सूची में कन्स्ट्रक्टर से फेंक दिया गया अपवाद पकड़ना चाहते हैं - तो सिंटैक्स वास्तव में बदसूरत हो जाता है - दोनों बुराइयों से मैं आईएसओके/ऑपर_बूल चुनता हूं। – sirgeorge

+0

लेकिन ऑब्जेक्ट का निर्माण करने के बाद वस्तु का प्रतिनिधित्व क्या होता है लेकिन इससे पहले कि मैं 'प्रारंभिक() 'कहूं? – ereOn

1

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

दूसरी तरफ, यदि निर्माण की सफलता या अन्यथा उपयोगकर्ता इनपुट पर निर्भर करता है, तो विफलता एक असाधारण स्थिति नहीं है, बल्कि सामान्य, अपेक्षित रनटाइम व्यवहार का हिस्सा है जिसके लिए आपको परीक्षण करने की आवश्यकता है। उस स्थिति में, आपके पास एक डिफ़ॉल्ट कन्स्ट्रक्टर होना चाहिए जो किसी ऑब्जेक्ट को "अमान्य" स्थिति में बनाता है, और प्रारंभिक फ़ंक्शन जिसे किसी भी निर्माता या बाद में कहा जा सकता है और यह सफल हो सकता है या नहीं भी हो सकता है। एक उदाहरण के रूप में std::ifstream लें।

तो अपने वर्ग के एक कंकाल ऐसा दिखाई दे सकता:

class Foo 
{ 
    bool valid; 
    bool initialize(Args... args) { /* ... */ } 

public: 
    Foo() : valid(false) { } 
    Foo(Args... args) : valid (false) { valid = initialize(args...); } 

    bool reset(Args... args) // atomic, doesn't change *this on failure 
    { 
     Foo other(args...); 
     if (other) { using std::swap; swap(*this, other); return true; } 
     return false; 
    } 

    explicit operator bool() const { return valid; } 
}; 
संबंधित मुद्दे