c++ - मुझे आईएसओ सी++ मानक अनुरूप अनुरूप कस्टम कैसे लिखना चाहिए और ऑपरेटरों को हटा देना चाहिए?
operator-overloading new-operator (3)
मुझे आईएसओ सी ++ मानक अनुरूप अनुरूप कस्टम कैसे लिखना चाहिए और ऑपरेटरों को delete
देना चाहिए?
यह नए अधिभार को जारी रखने और निरंतर रोशनी सी ++ एफएक्यू, ऑपरेटर अधिभार , और इसके अनुवर्ती अनुमोदन में निरंतर है, क्यों एक को डिफ़ॉल्ट नया बदलना चाहिए और ऑपरेटरों को हटा देना चाहिए?
धारा 1: मानक-अनुरूप new
ऑपरेटर को लिखना
- भाग 1: एक कस्टम
new
ऑपरेटर लिखने के लिए आवश्यकताओं को समझना - भाग 2:
new_handler
आवश्यकताओं को समझना - भाग 3: विशिष्ट परिदृश्य आवश्यकताओं को समझना
सेक्शन 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()
को बार-बार कहा जाएगा। इसलिए, हैंडलर को यह सुनिश्चित करना चाहिए कि अगला आवंटन सफल हो जाए, या किसी अन्य हैंडलर को पंजीकृत करें, या कोई हैंडलर पंजीकृत न करें, या वापस न आएं (यानी प्रोग्राम को समाप्त करें)। यदि कोई नया हैंडलर नहीं है और आवंटन विफल रहता है, तो ऑपरेटर अपवाद फेंक देगा।
भाग 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;
}
};