c++ - मुझे आईएसओ सी++ मानक अनुरूप अनुरूप कस्टम कैसे लिखना चाहिए और ऑपरेटरों को हटा देना चाहिए?




operator-overloading new-operator (3)

मुझे आईएसओ सी ++ मानक अनुरूप अनुरूप कस्टम कैसे लिखना चाहिए और ऑपरेटरों को delete देना चाहिए?

यह नए अधिभार को जारी रखने और निरंतर रोशनी सी ++ एफएक्यू, ऑपरेटर अधिभार , और इसके अनुवर्ती अनुमोदन में निरंतर है, क्यों एक को डिफ़ॉल्ट नया बदलना चाहिए और ऑपरेटरों को हटा देना चाहिए?

धारा 1: मानक-अनुरूप new ऑपरेटर को लिखना

सेक्शन 2: एक मानक-अनुरूप delete ऑपरेटर लिखना

(नोट: यह स्टैक ओवरफ्लो के सी ++ एफएक्यू में प्रवेश करने के लिए है । यदि आप इस फॉर्म में एक एफएक्यू प्रदान करने के विचार की आलोचना करना चाहते हैं, तो मेटा पर पोस्ट करना जो यह सब शुरू कर देगा, ऐसा करने का स्थान होगा। उस प्रश्न की निगरानी सी ++ चैटरूम में की जाती है, जहां एफएक्यू विचार पहली जगह शुरू हुआ था, इसलिए आपके उत्तर को उन लोगों द्वारा पढ़ा जाने की संभावना है जो इस विचार के साथ आए थे।)
नोट: उत्तर स्कॉट मेयर्स के अधिक प्रभावी सी ++ और आईएसओ सी ++ मानक से सीखने पर आधारित है।


भाग I

इस सी ++ एफएक्यू एंट्री ने समझाया कि क्यों कोई new अधिभारित करना चाहता है और ऑपरेटरों को अपनी कक्षा के लिए delete सकता है। यह वर्तमान एफएक्यू यह बताने की कोशिश करता है कि एक मानक-अनुरूप तरीके से ऐसा कैसे करता है।

एक कस्टम new ऑपरेटर को कार्यान्वित करना

सी ++ मानक (§18.4.1.1) operator new रूप में परिभाषित करता है:

void* operator new (std::size_t size) throw (std::bad_alloc);

सी ++ मानक अर्थशास्त्र को निर्दिष्ट करता है कि इन ऑपरेटरों के कस्टम संस्करणों को §3.7.3 और §18.4.1 में पालन करना होगा

आइए आवश्यकताओं को सारांशित करें।

आवश्यकता # 1: इसे गतिशील रूप से स्मृति के कम से कम size बाइट आवंटित करना चाहिए और आवंटित स्मृति में एक सूचक वापस करना चाहिए। सी ++ मानक से उद्धरण, खंड 3.7.4.1.3:

आवंटन समारोह भंडारण की अनुरोधित राशि आवंटित करने का प्रयास करता है। यदि यह सफल होता है, तो यह भंडारण के एक ब्लॉक की शुरुआत के पते को वापस कर देगा, जिसका बाइट्स लंबाई लंबाई के रूप में कम से कम होगा ...

मानक आगे लगाता है:

... लौटा दिया गया सूचकांक उपयुक्त रूप से गठबंधन किया जाएगा ताकि इसे किसी भी पूर्ण ऑब्जेक्ट प्रकार के सूचक में परिवर्तित किया जा सके और उसके बाद आवंटित स्टोरेज में ऑब्जेक्ट या सरणी तक पहुंचने के लिए उपयोग किया जा सके (जब तक भंडारण को किसी कॉल द्वारा स्पष्ट रूप से अस्वीकार नहीं किया जाता deallocation समारोह)। भले ही अनुरोध किए गए स्थान का आकार शून्य है, अनुरोध विफल हो सकता है। यदि अनुरोध सफल होता है, तो लौटाए गए मान को पहले से लौटाए गए मान पी 1 से अलग गैर-शून्य सूचक मूल्य (4.10) पी 0 होगा, जब तक कि मान पी 1 उप-क्रमशः ऑपरेटर को delete दिया गया हो।

यह हमें और महत्वपूर्ण आवश्यकताओं देता है:

आवश्यकता # 2: स्मृति आवंटन फ़ंक्शन जो हम उपयोग करते हैं (आमतौर पर malloc() या कुछ अन्य कस्टम आवंटक) को आवंटित स्मृति में एक उपयुक्त गठबंधन सूचक को वापस करना चाहिए, जिसे पूर्ण ऑब्जेक्ट प्रकार के सूचक में परिवर्तित किया जा सकता है और ऑब्जेक्ट तक पहुंचने के लिए उपयोग किया जा सकता है ।

आवश्यकता # 3: शून्य बाइट्स का अनुरोध होने पर भी हमारे कस्टम ऑपरेटर को एक वैध सूचक वापस करना होगा।

स्पष्ट प्रोटोटाइप से अनुमानित सुविधाओं में से एक यह है कि:

आवश्यकता # 4: यदि new अनुरोधित आकार की गतिशील स्मृति आवंटित नहीं कर सकता है, तो इसे टाइप std::bad_alloc अपवाद को फेंक देना चाहिए।

परंतु! आंखों को पूरा करने के मुकाबले इसके लिए और भी कुछ है: यदि आप new ऑपरेटर documentation (मानक से उद्धरण आगे नीचे आते हैं) पर नज़र डालें, तो यह कहता है:

यदि set_new_handler का उपयोग new_handler फ़ंक्शन को परिभाषित करने के लिए किया गया है, तो यह new_handler फ़ंक्शन operator new को मानक डिफ़ॉल्ट परिभाषा द्वारा बुलाया जाता है यदि यह अनुरोधित संग्रहण को स्वयं ही आवंटित नहीं कर सकता है।

इस आवश्यकता को पूरा करने के लिए हमारी कस्टम new जरूरतों को समझने के लिए, हमें समझना चाहिए:

new_handler और set_new_handler क्या है?

new_handler एक फ़ंक्शन के लिए एक new_handler है जो किसी फ़ंक्शन को लेता है और कुछ भी नहीं देता है, और set_new_handler एक ऐसा फ़ंक्शन है जो एक new_handler लेता है और देता है।

set_new_handler का पैरामीटर फ़ंक्शन ऑपरेटर को पॉइंटर है, अगर उसे अनुरोधित मेमोरी आवंटित नहीं किया जा सकता है तो उसे कॉल करना चाहिए। इसका रिटर्न वैल्यू पहले पंजीकृत हैंडलर फ़ंक्शन के लिए एक पॉइंटर है, या अगर पिछले हैंडलर नहीं था तो शून्य।

चीजों को स्पष्ट करने के लिए कोड नमूने के लिए एक उपयुक्त क्षण:

#include <iostream>
#include <cstdlib>

// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
    std::cerr << "Unable to satisfy request for memory\n";

    std::abort();
}

int main()
{
    //set the new_handler
    std::set_new_handler(outOfMemHandler);

    //Request huge memory size, that will cause ::operator new to fail
    int *pBigDataArray = new int[100000000L];

    return 0;
}

उपर्युक्त उदाहरण में, operator new (सबसे अधिक संभावना) 100,000,000 पूर्णांक के लिए स्थान आवंटित करने में असमर्थ होगा, और फ़ंक्शन outOfMemHandler() को कॉल किया जाएगा, और त्रुटि संदेश जारी करने के बाद प्रोग्राम निरस्त हो जाएगा।

यहां ध्यान रखना महत्वपूर्ण है कि जब operator new मेमोरी अनुरोध पूरा करने में असमर्थ होता है, तो यह new-handler फ़ंक्शन को बार new-handler बार कॉल करता है जब तक कि उसे पर्याप्त मेमोरी मिलती है या कोई नया हैंडलर नहीं होता है। उपर्युक्त उदाहरण में, जब तक हम std::abort() कॉल नहीं करते हैं, outOfMemHandler() को बार-बार कहा जाएगा। इसलिए, हैंडलर को यह सुनिश्चित करना चाहिए कि अगला आवंटन सफल हो जाए, या किसी अन्य हैंडलर को पंजीकृत करें, या कोई हैंडलर पंजीकृत न करें, या वापस न आएं (यानी प्रोग्राम को समाप्त करें)। यदि कोई नया हैंडलर नहीं है और आवंटन विफल रहता है, तो ऑपरेटर अपवाद फेंक देगा।

निरंतरता 1


भाग III

... जारी रखा

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

इसके अलावा, हमारे पास एक अनंत लूप है और लूप से बाहर निकलने का एकमात्र तरीका स्मृति को सफलतापूर्वक आवंटित करने के लिए है, या नए हैंडलिंग फ़ंक्शन के लिए हमने पहले अनुमान लगाया है। जब तक new_handler उन चीजों में से एक नहीं करता है, तो new ऑपरेटर के अंदर यह लूप कभी समाप्त नहीं होगा।

एक चेतावनी: ध्यान दें कि मानक ( §3.7.4.1.3 , ऊपर उद्धृत) स्पष्ट रूप से यह नहीं कहता है कि अधिभारित new ऑपरेटर को अनंत लूप लागू करना होगा , लेकिन यह केवल इतना कहता है कि यह डिफ़ॉल्ट व्यवहार है। तो यह विवरण व्याख्या के लिए खुला है, लेकिन अधिकांश कंपाइलर्स ( GCC और माइक्रोसॉफ्ट विजुअल सी ++ ) इस लूप कार्यक्षमता को कार्यान्वित करते हैं (आप पहले प्रदान किए गए कोड नमूने संकलित कर सकते हैं)। इसके अलावा, चूंकि स्कॉट मेयर्स जैसे सी ++ लेखक इस दृष्टिकोण को सुझाते हैं, यह उचित है।

विशेष परिदृश्य

आइए निम्नलिखित परिदृश्य पर विचार करें।

class Base
{
    public:
        static void * operator new(std::size_t size) throw(std::bad_alloc);
};

class Derived: public Base
{
   //Derived doesn't declare operator new
};

int main()
{
    // This calls Base::operator new!
    Derived *p = new Derived;

    return 0;
}

इस FAQ के रूप में, बताता है कि एक कस्टम मेमोरी मैनेजर लिखने का एक आम कारण किसी वर्ग या उसके व्युत्पन्न वर्गों के लिए नहीं, एक विशिष्ट वर्ग की वस्तुओं के लिए आवंटन को अनुकूलित करना है, जिसका मूल रूप से मतलब है कि बेस ऑपरेटर के लिए हमारा ऑपरेटर नया है आकार के आकार sizeof(Base) की वस्तुओं के लिए ट्यून किया गया - कुछ भी बड़ा नहीं और कुछ भी छोटा नहीं।

उपर्युक्त नमूने में, विरासत के कारण व्युत्पन्न वर्ग Derived बेस क्लास के नए ऑपरेटर को प्राप्त करता है। यह एक व्युत्पन्न वर्ग के किसी ऑब्जेक्ट के लिए स्मृति आवंटित करने के लिए बेस क्लास में कॉलिंग ऑपरेटर को नया बनाता है। इस परिस्थिति को संभालने के लिए हमारे operator new तरीका सबसे अच्छा तरीका है कि इस तरह के कॉल को मानक ऑपरेटर को "गलत" मात्रा में स्मृति की मांग करने का अनुरोध करें, जैसे:

void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{
    if (size != sizeof(Base))          // If size is "wrong,", that is, != sizeof Base class
    {
         return ::operator new(size);  // Let std::new handle this request
    }
    else
    {
         //Our implementation
    }
}

ध्यान दें कि आकार के लिए चेक भी हमारी आवश्यकता # 3 को शामिल करता है । ऐसा इसलिए है क्योंकि सभी फ्रीस्टैंडिंग ऑब्जेक्ट्स में सी ++ में गैर-शून्य आकार होता है, इसलिए sizeof(Base) शून्य कभी नहीं हो सकता है, इसलिए यदि आकार शून्य है, तो अनुरोध ::operator new को अग्रेषित किया जाएगा, और यह गारंटी है कि यह संभालेगा यह मानक अनुपालन में है।

उद्धरण: सी ++ के निर्माता से, डॉ बजेर्ने स्ट्रास्ट्रुप।


कस्टम डिलीट ऑपरेटर को कार्यान्वित करना

सी ++ मानक ( §18.4.1.1 ) लाइब्रेरी operator delete है:

void operator delete(void*) throw();

आइए हम अपने कस्टम operator delete लिए आवश्यकताओं को इकट्ठा करने के अभ्यास को दोहराएं:

आवश्यकता # 1: यह void वापस आ जाएगी और इसका पहला पैरामीटर void* । एक कस्टम delete operator पास एक से अधिक पैरामीटर भी हो सकते हैं लेकिन अच्छी तरह से आवंटित स्मृति को इंगित करने वाले पॉइंटर को पास करने के लिए हमें केवल एक पैरामीटर चाहिए।

सी ++ मानक से उद्धरण:

धारा §3.7.3.2.2:

"प्रत्येक डीलोकेशन फ़ंक्शन शून्य हो जाएगा और उसका पहला पैरामीटर शून्य हो जाएगा *। एक डिलीओशन फ़ंक्शन में एक से अधिक पैरामीटर हो सकते हैं ....."

आवश्यकता # 2: यह गारंटी देनी चाहिए कि एक तर्क के रूप में पारित एक शून्य सूचक को हटाना सुरक्षित है।

सी ++ मानक से उद्धरण: खंड §3.7.3.2.3:

मानक लाइब्रेरी में प्रदान किए गए डेलोकेशन फ़ंक्शंस में से एक को प्रदान किए गए पहले तर्क का मान शून्य सूचक मान हो सकता है; यदि हां, तो डीलोकेशन फ़ंक्शन पर कॉल का कोई प्रभाव नहीं पड़ता है। अन्यथा, मानक लाइब्रेरी में operator delete(void*) को प्रदान किया गया मान मानक लाइब्रेरी में operator new(size_t) या operator new(size_t, const std::nothrow_t&) पिछले आमंत्रण द्वारा लौटाए गए मानों में से एक होगा। , और मानक लाइब्रेरी में operator delete[](void*) गए मान को operator new[](size_t) या operator new[](size_t, const std::nothrow_t&) पिछले आमंत्रण द्वारा वापस किए गए मानों में से एक होगा operator new[](size_t, const std::nothrow_t&) मानक पुस्तकालय में।

आवश्यकता # 3: यदि पॉइंटर पास किया जा रहा है तो null नहीं है, तो delete operator को आवंटित गतिशील स्मृति को आवंटित करना चाहिए और पॉइंटर को असाइन करना चाहिए।

सी ++ मानक से उद्धरण: खंड §3.7.3.2.4:

यदि मानक लाइब्रेरी में एक डेलोकेशन फ़ंक्शन को दिया गया तर्क एक सूचक है जो शून्य सूचक मान (4.10) नहीं है, तो डेलोकेशन फ़ंक्शन पॉइंटर द्वारा संदर्भित संग्रहण को हटा देगा, अमान्य सभी पॉइंटर्स को किसी भी हिस्से के संदर्भ में अमान्य प्रस्तुत करेगा हटाया भंडारण।

आवश्यकता # 4: इसके अलावा, चूंकि हमारे क्लास-विशिष्ट ऑपरेटर ने "गलत" आकार के ::operator new अनुरोधों के अनुरोध के बाद, हमें "गलत आकार" हटाने के अनुरोधों को ::operator delete लिए आगे बढ़ाना होगा।

इसलिए यहां ऊपर दी गई आवश्यकताओं के आधार पर एक कस्टम delete operator के लिए एक मानक अनुरूप छद्म कोड है:

class Base
{
    public:
        //Same as before
        static void * operator new(std::size_t size) throw(std::bad_alloc);
        //delete declaration
        static void operator delete(void *rawMemory, std::size_t size) throw();

        void Base::operator delete(void *rawMemory, std::size_t size) throw()
        {
            if (rawMemory == 0)
            {
                return;                            // No-Op is null pointer
            }

            if (size != sizeof(Base))
            {
                // if size is "wrong,"
                ::operator delete(rawMemory);      //Delegate to std::delete
                return;
            }
            //If we reach here means we have correct sized pointer for deallocation
            //deallocate the memory pointed to by rawMemory;

            return;
        }
};




delete-operator