C++
RAII: संसाधन अधिग्रहण प्रारंभिक है
खोज…
टिप्पणियों
RAII का मतलब R esource एक cquisition I s I nitialization है। इसके अलावा कभी-कभी SBRM (स्कोप-आधारित संसाधन प्रबंधन) या RRID (रिसोर्स रिलीज़ इज़ डिस्ट्रक्शन) के रूप में जाना जाता है, RAII एक मुहावरा है जिसका उपयोग संसाधनों को आजीवन बाँधने के लिए किया जाता है। C ++ में, किसी ऑब्जेक्ट के लिए विध्वंसक हमेशा चलता रहता है जब कोई ऑब्जेक्ट कार्यक्षेत्र से बाहर चला जाता है - हम इसका फायदा उठा सकते हैं ताकि संसाधन क्लीनअप को ऑब्जेक्ट विनाश में बाँध सकें।
किसी भी समय आपको कुछ संसाधन प्राप्त करने की आवश्यकता होती है (जैसे एक ताला, एक फ़ाइल संभाल, एक आवंटित बफर) जिसे आपको अंततः जारी करने की आवश्यकता होगी, आपको अपने लिए उस संसाधन प्रबंधन को संभालने के लिए किसी ऑब्जेक्ट का उपयोग करने पर विचार करना चाहिए। स्टैक अनइंडिंग अपवाद या शुरुआती दायरे से बाहर निकलने की परवाह किए बिना होगा, इसलिए संसाधन हैंडलर ऑब्जेक्ट आपके लिए संसाधन को साफ कर देगा, क्योंकि आप सभी संभावित वर्तमान और भविष्य के कोड पथों पर सावधानीपूर्वक विचार कर सकते हैं।
यह ध्यान देने योग्य है कि RAII संसाधनों के जीवनकाल के बारे में सोचने वाले डेवलपर को पूरी तरह से मुक्त नहीं करता है। एक मामला है, जाहिर है, एक दुर्घटना या निकास () कॉल, जो विनाशकारियों को बुलाया जाने से रोकेगा। चूंकि OS प्रक्रिया-स्थानीय संसाधनों को स्मृति की तरह साफ कर देगा, एक प्रक्रिया समाप्त होने के बाद, यह ज्यादातर मामलों में समस्या नहीं है। हालाँकि सिस्टम रिसोर्स (यानी नामित पाइप्स, लॉक फाइल्स, शेयर्ड मेमोरी) के साथ आपको उस मामले से निपटने के लिए सुविधाओं की आवश्यकता होती है, जहाँ कोई प्रक्रिया अपने आप साफ नहीं होती, यानी स्टार्टअप टेस्ट पर अगर लॉक फ़ाइल है, तो यह है, पीआईडी के साथ प्रक्रिया को वास्तव में सत्यापित करें, फिर उसके अनुसार कार्य करें।
एक और स्थिति तब होती है जब एक यूनिक्स प्रक्रिया नई प्रक्रिया बनाने के लिए एक फोर्क-एक्जीक्यूट के बाद, निष्पादन-परिवार से एक फ़ंक्शन को बुलाती है। यहां, बच्चे की प्रक्रिया में माता-पिता की मेमोरी (RAII ऑब्जेक्ट्स सहित) की पूरी प्रतिलिपि होगी, लेकिन एक बार निष्पादन को बुलाया गया था, उस प्रक्रिया में कोई भी विध्वंसक नहीं कहा जाएगा। दूसरी ओर, यदि किसी प्रक्रिया को कांटा जाता है और न ही प्रक्रियाओं को निष्पादित किया जाता है, तो दोनों प्रक्रियाओं में सभी संसाधनों को साफ किया जाता है। यह केवल उन सभी संसाधनों के लिए सही है जो वास्तव में कांटे में दोहराए गए थे, लेकिन सिस्टम संसाधनों के साथ, दोनों प्रक्रियाओं में केवल संसाधन (यानी लॉक फ़ाइल का पथ) का संदर्भ होगा और दोनों इसे व्यक्तिगत रूप से जारी करने का प्रयास करेंगे, संभावित रूप से अन्य प्रक्रिया विफल।
ताला
खराब लॉकिंग:
std::mutex mtx;
void bad_lock_example() {
mtx.lock();
try
{
foo();
bar();
if (baz()) {
mtx.unlock(); // Have to unlock on each exit point.
return;
}
quux();
mtx.unlock(); // Normal unlock happens here.
}
catch(...) {
mtx.unlock(); // Must also force unlock in the presence of
throw; // exceptions and allow the exception to continue.
}
}
यह म्यूटेक्स के लॉकिंग और अनलॉकिंग को लागू करने का गलत तरीका है। unlock()
साथ म्यूटेक्स की सही रिलीज सुनिश्चित करने के लिए unlock()
प्रोग्रामर को यह सुनिश्चित करने की आवश्यकता है कि फ़ंक्शन के बाहर निकलने के परिणामस्वरूप सभी कॉल unlock()
करने के लिए कॉल करें unlock()
। जैसा कि ऊपर दिखाया गया है, यह एक भंगुर प्रक्रिया है क्योंकि इसे किसी भी अनुचर को मैन्युअल रूप से अनुसरण करते रहने की आवश्यकता होती है।
RAII को लागू करने के लिए उचित रूप से तैयार की गई कक्षा का उपयोग करना, समस्या तुच्छ है:
std::mutex mtx;
void good_lock_example() {
std::lock_guard<std::mutex> lk(mtx); // constructor locks.
// destructor unlocks. destructor call
// guaranteed by language.
foo();
bar();
if (baz()) {
return;
}
quux();
}
lock_guard
एक अत्यंत सरल वर्ग टेम्पलेट है जो बस इसके निर्माता में तर्क पर lock()
कॉल करता है, तर्क का संदर्भ रखता है, और इसके विध्वंसक में तर्क पर unlock()
कॉल करता है। यही है, जब lock_guard
दायरे से बाहर हो जाता है, तो mutex
को अनलॉक करने की गारंटी दी जाती है। इससे कोई फर्क नहीं पड़ता कि यदि यह कार्यक्षेत्र से बाहर हो गया है तो यह एक अपवाद है या जल्दी वापसी - सभी मामलों को संभाला जाता है; नियंत्रण प्रवाह की परवाह किए बिना, हमने गारंटी दी है कि हम सही ढंग से अनलॉक करेंगे।
अंत में / ScopeExit
ऐसे मामलों के लिए जब हम कुछ संसाधनों को संभालने के लिए विशेष कक्षाएं नहीं लिखना चाहते हैं, हम एक सामान्य वर्ग लिख सकते हैं:
template<typename Function>
class Finally final
{
public:
explicit Finally(Function f) : f(std::move(f)) {}
~Finally() { f(); } // (1) See below
Finally(const Finally&) = delete;
Finally(Finally&&) = default;
Finally& operator =(const Finally&) = delete;
Finally& operator =(Finally&&) = delete;
private:
Function f;
};
// Execute the function f when the returned object goes out of scope.
template<typename Function>
auto onExit(Function &&f) { return Finally<std::decay_t<Function>>{std::forward<Function>(f)}; }
और इसका उदाहरण उपयोग
void foo(std::vector<int>& v, int i)
{
// ...
v[i] += 42;
auto autoRollBackChange = onExit([&](){ v[i] -= 42; });
// ... code as recursive call `foo(v, i + 1)`
}
नोट (1): विध्वंसक परिभाषा के बारे में कुछ चर्चा को अपवाद को संभालने पर विचार करना होगा:
-
~Finally() noexcept { f(); }
:std::terminate
को अपवाद के मामले में कहा जाता है -
~Finally() noexcept(noexcept(f())) { f(); }
: समाप्त करना () केवल स्टैक अनइंडिंग के दौरान अपवाद के मामले में कहा जाता है। -
~Finally() noexcept { try { f(); } catch (...) { /* ignore exception (might log it) */} }
कोईstd::terminate
कहा जाता है, लेकिन हम त्रुटि को संभाल नहीं सकते (यहां तक कि गैर स्टैक अनइंडिंग के लिए भी)।
स्कोपसुअसे (c ++ 17)
int std::uncaught_exceptions()
के लिए धन्यवाद, हम कार्रवाई को लागू कर सकते हैं जो केवल सफलता पर लागू होती है (गुंजाइश में कोई अपवाद नहीं)। पहले bool std::uncaught_exception()
केवल यह पता लगाने की अनुमति देता है कि क्या कोई स्टैक अनइंडिंग चल रहा है।
#include <exception>
#include <iostream>
template <typename F>
class ScopeSuccess
{
private:
F f;
int uncaughtExceptionCount = std::uncaught_exceptions();
public:
explicit ScopeSuccess(const F& f) : f(f) {}
ScopeSuccess(const ScopeSuccess&) = delete;
ScopeSuccess& operator =(const ScopeSuccess&) = delete;
// f() might throw, as it can be caught normally.
~ScopeSuccess() noexcept(noexcept(f())) {
if (uncaughtExceptionCount == std::uncaught_exceptions()) {
f();
}
}
};
struct Foo {
~Foo() {
try {
ScopeSuccess logSuccess{[](){std::cout << "Success 1\n";}};
// Scope succeeds,
// even if Foo is destroyed during stack unwinding
// (so when 0 < std::uncaught_exceptions())
// (or previously std::uncaught_exception() == true)
} catch (...) {
}
try {
ScopeSuccess logSuccess{[](){std::cout << "Success 2\n";}};
throw std::runtime_error("Failed"); // returned value
// of std::uncaught_exceptions increases
} catch (...) { // returned value of std::uncaught_exceptions decreases
}
}
};
int main()
{
try {
Foo foo;
throw std::runtime_error("Failed"); // std::uncaught_exceptions() == 1
} catch (...) { // std::uncaught_exceptions() == 0
}
}
आउटपुट:
Success 1
स्कोप फ़ेल (c ++ 17)
int std::uncaught_exceptions()
के लिए धन्यवाद, हम कार्रवाई को लागू कर सकते हैं जो केवल विफलता पर लागू होती है (गुंजाइश में अपवाद को फेंक दिया जाता है)। पहले bool std::uncaught_exception()
केवल यह पता लगाने की अनुमति देता है कि क्या कोई स्टैक अनइंडिंग चल रहा है।
#include <exception>
#include <iostream>
template <typename F>
class ScopeFail
{
private:
F f;
int uncaughtExceptionCount = std::uncaught_exceptions();
public:
explicit ScopeFail(const F& f) : f(f) {}
ScopeFail(const ScopeFail&) = delete;
ScopeFail& operator =(const ScopeFail&) = delete;
// f() should not throw, else std::terminate is called.
~ScopeFail() {
if (uncaughtExceptionCount != std::uncaught_exceptions()) {
f();
}
}
};
struct Foo {
~Foo() {
try {
ScopeFail logFailure{[](){std::cout << "Fail 1\n";}};
// Scope succeeds,
// even if Foo is destroyed during stack unwinding
// (so when 0 < std::uncaught_exceptions())
// (or previously std::uncaught_exception() == true)
} catch (...) {
}
try {
ScopeFail logFailure{[](){std::cout << "Failure 2\n";}};
throw std::runtime_error("Failed"); // returned value
// of std::uncaught_exceptions increases
} catch (...) { // returned value of std::uncaught_exceptions decreases
}
}
};
int main()
{
try {
Foo foo;
throw std::runtime_error("Failed"); // std::uncaught_exceptions() == 1
} catch (...) { // std::uncaught_exceptions() == 0
}
}
आउटपुट:
Failure 2