2011-06-11 6 views
16

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

लेकिन सी ++ 0x चालक कन्स्ट्रक्टर के साथ मैं एक समस्या में भाग लेता हूं। चूंकि विनाशक को "अनलोड" ऑब्जेक्ट पर भी बुलाया जाता है, इसलिए मुझे संसाधन हैंडल की सफाई को रोकने की आवश्यकता है। चूंकि सदस्य चर है, इसलिए मेरे पास विनाशक को इंगित करने के लिए मान -1 या INVALID_HANDLE (या समकक्ष मान) असाइन करने का कोई तरीका नहीं है कि उसे कुछ भी नहीं करना चाहिए।

क्या कोई तरीका है कि किसी ऑब्जेक्ट की स्थिति किसी अन्य ऑब्जेक्ट में स्थानांतरित हो जाने पर विनाशक को नहीं कहा जाता है?

उदाहरण:

class File 
{ 
public: 
    // Kind of "named constructor" or "static factory method" 
    static File open(const char *fileName, const char *modes) 
    { 
     FILE *handle = fopen(fileName, modes); 
     return File(handle); 
    } 

private: 
    FILE * const handle; 

public: 
    File(FILE *handle) : handle(handle) 
    { 
    } 

    ~File() 
    { 
     fclose(handle); 
    } 

    File(File &&other) : handle(other.handle) 
    { 
     // The compiler should not call the destructor of the "other" 
     // object. 
    } 

    File(const File &other) = delete; 
    File &operator =(const File &other) = delete; 
}; 

उत्तर

7

नहीं, यह करने के लिए कोई रास्ता नहीं है। मैं सुझाव दूंगा कि यदि आप वास्तव में handle परिवर्तनीय होने के साथ जुड़े हुए हैं तो आपके पास एक गैर-कॉन्स ध्वज सदस्य चर होना चाहिए जो इंगित करता है कि विनाश कुछ भी करना चाहिए या नहीं।

+1

आपका उत्तर निश्चित रूप से जाने का एक तरीका है। लेकिन सी ++ 0x से संबंधित मुझे शैली पसंद नहीं है कि विनाशकों को यह जांचना है कि क्या वाकई वास्तव में होना चाहिए। क्या उन्हें यह नहीं मानना ​​चाहिए कि वस्तु पूरी तरह से चल रही है और चल रही है और अब विनाश का मुद्दा है? – mazatwork

+1

@mazatwork: ठीक है, इस तरह से सोचो। मान लीजिए कि आपके पास एक जटिल वस्तु थी जो कई अलग-अलग राज्यों में हो सकती है कि प्रत्येक को विनाशकों का एक अलग सेट चाहिए। उदाहरण के लिए, उदाहरण के लिए, एक कैश है जो आरंभ किया जा सकता है या नहीं भी हो सकता है, या एक डेटाबेस कनेक्शन जो बंद होने की आवश्यकता हो सकती है या नहीं। क्या आप वास्तव में विनाशकारी नहीं हैं जब आप डेटाबेस कनेक्शन को बंद नहीं करते हैं जो आपके विनाशक में खुला नहीं है? बिलकूल नही। यह मूल रूप से एक ही बात है। आप अभी भी विनाश कर रहे हैं, यह सिर्फ इतना है कि जिस वस्तु में वस्तु है वह अधिक काम नहीं करती है। – Omnifarious

+0

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

3

एक चालक कन्स्ट्रक्टर को लागू करने का सामान्य तरीका उदाहरण के सदस्यों को शून्य या अन्यथा अमान्य करना है (उदाहरण के लिए MSDN देखें)। इसलिए मैं कहूंगा कि const का उपयोग न करें क्योंकि यह चाल semantics के लक्ष्यों के साथ असंगत है।

+0

ठीक है, आप वास्तव में 'const' का उपयोग कर सकते हैं और संकलक अभी भी चालक निर्माता उत्पन्न करेगा (बेशक, यदि [सभी आवश्यकताओं] (https://stackoverflow.com/a/8285499) संतुष्ट हैं)। यहां साबित करने के लिए एक उदाहरण दिया गया है: [codepad.org/Xh4va2eR](http://codepad.org/Xh4va2eR) (कोडपैड पुरानी कंपाइलर त्रुटियों को अनदेखा करें ..) –

13

यही कारण है कि आपको सदस्य चर const घोषित नहीं करना चाहिए। const सदस्य चर आमतौर पर कोई उद्देश्य नहीं देते हैं। यदि आप नहीं चाहते हैं कि उपयोगकर्ता FILE* को म्यूटेट करें, तो उन्हें ऐसा करने के लिए फ़ंक्शंस प्रदान न करें, और यदि आप इसे दुर्घटना से इसे म्यूट करने से रोकना चाहते हैं, तो अपने कार्यों को const पर चिह्नित करें। हालांकि, सदस्य चर को स्वयं const न बनाएं - क्योंकि तब आप मज़े में चलाते हैं जब आप चाल का उपयोग करना शुरू करते हैं या सेमेन्टिक्स कॉपी करते हैं।

+7

कॉन्स्ट सदस्य चर उपयोगी हैं जैसे संदर्भ हैं। अक्सर आप एक मान रखना चाहते हैं और आप जानते हैं कि आप इसे नहीं बदलेंगे, जो संकलक द्वारा कुछ अनुकूलन की अनुमति दे सकता है। विधियों के रूप में विधियों को चिह्नित करना केवल तब तक काम करेगा जब तक मेरे पास कुछ परिवर्तनीय चर नहीं हैं। – mazatwork

+3

यह वास्तव में मेरे अनुभव में किसी भी उपयोगी कंपाइलर अनुकूलन की अनुमति नहीं देता है। और यह कई बहुत उपयोगी अर्थशास्त्र को रोकता है। – Puppy

+0

बिल्कुल सच है! मैं ** मजेदार ** में भाग गया क्योंकि मैंने एक अंतर्निहित वर्ग के सदस्य को कॉन्स सदस्य के रूप में घोषित किया था। – user8385554

-1

संदर्भ गणना मानक दृष्टिकोण है जो आपकी समस्या का समाधान करता है। अपनी कक्षा में संदर्भ गिनती जोड़ने पर विचार करें; या तो मैन्युअल रूप से, या मौजूदा टूल्स जैसे boost shared_ptr का उपयोग करना।

+2

यह समस्या से उत्पन्न एक ऑर्थोगोनल विषय है (जो 'फाइलों' को स्थानांतरित कर रहा है)। आप जो प्रस्तावित करते हैं वह 'shared_ptr ' बना रहा है। उपयुक्त हो सकता है, लेकिन शायद नहीं, और इसमें निश्चित रूप से एक बड़ा डिजाइन परिवर्तन शामिल होगा –

0

असल में, मैंने आज भी इस समस्या को अपने साथ चलाया है। स्वीकार करने के लिए 'नहीं किया जा सकता' & 'उपयोग shared_ptr/संदर्भ गिनती', और अधिक googling के लिए तैयार नहीं है, मैं इस आधार वर्ग के साथ आया था:

class Resource 
{ 
private: 
    mutable bool m_mine; 

protected: 
    Resource() 
    : m_mine(true) 
    { 
    } 

    Resource(const Resource&)  = delete; 
    void operator=(const Resource&) = delete; 

    Resource(const Resource&& other) 
    : m_mine(other.m_mine) 
    { 
     other.m_mine = false; 
    } 

    bool isMine() const 
    { 
     return m_mine; 
    } 
}; 

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

class A : protected Resource 
{ 
private: 
    const int m_i; 

public: 
    A() 
    : m_i(0) 
    { 
    } 

    A(const int i) 
    : m_i(i) 
    { 
    } 

    A(const A&& a) 
    : Resource(std::move(a )) 
    , m_i  (std::move(a.m_i)) // this is a move iff member has const move constructor, copy otherwise 
    { 
    } 

    ~A() 
    { 
     if (isMine()) 
     { 
      // Free up resources. Executed only for non-moved objects 
      cout << "A destructed" << endl; 
     } 
    } 
}; 

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

struct B 
{ 
    const A m_a; 
    const X m_x; 

    B(const A&& a, const X& x) // implement this way only if X has copy constructor; otherwise do for 'x' like we do for 'a' 
    : m_a(std::move(a)) 
    , m_x(   x ) 
    { 
    } 

    B(const B&& b) 
    : m_a(std::move(b.m_a)) 
    , m_x(std::move(b.m_x)) // this is a move iff X has move constructor, copy otherwise 
    { 
    } 

    ~B() 
    { 
     cout << "B destructed" << endl; 
    } 
}; 

ध्यान दें कि बी के क्षेत्र भी स्थिरांक हो सकता है:

फिर, यह सोचते हैं कि आप एक struct X है। हमारे चालक रचनाकार हैं। आपके प्रकारों को उचित चालक रचनाकारों को देखते हुए, किसी भी ढेर-आवंटित स्मृति को वस्तुओं के बीच साझा किया जा सकता है।