2015-09-17 6 views
14

स्ट्रॉस्ट्रप की "द सी ++ प्रोग्रामिंग भाषा" से एक कोड यहां दिया गया है जो finally लागू करता है जिसे मैं समझ नहीं सकता कि विनाशक कहां कहलाता है।सी ++ में किसी फ़ंक्शन से लौटे अस्थायी वस्तुओं के लिए विनाशक को कैसे बुलाया जाता है?

template<typename F> struct Final_action 
{ 
    Final_action(F f): clean{f} {} 
    ~Final_action() { clean(); } 
    F clean; 
} 

template<class F> 
Final_action<F> finally(F f) 
{ 
    return Final_action<F>(f); 
} 

void test(){ 
    int* p=new int{7}; 
    auto act1 = finally([&]{delete p;cout<<"Goodbye,cruel world\n";}); 
} 

मैं इस के आसपास दो प्रश्न हैं:

  1. लेखक के अनुसार, delete p केवल एक बार कहा जाता हो जाता है: जब Act1 क्षेत्र से बाहर चला जाता है। लेकिन मेरी समझ से: पहला, act1 प्रतिलिपि निर्माता, तो समारोह में अस्थायी वस्तु Final_action<F>(f)finally विलुप्त हो जाता है के साथ, प्रारंभ की जाएगी समारोह test के अंत में पहली बार के लिए delete p बुला, फिर दूसरी बार जब act1 बाहर है दायरे का मुझे यह गलत कहां मिल रहा है?

  2. finally फ़ंक्शन क्यों आवश्यक है? क्या मैं सिर्फ Final_action act1([&]{delete p;cout<<"Goodbye,cruel world\n"}) परिभाषित नहीं कर सकता? क्या वही है?

इसके अलावा, अगर कोई बेहतर शीर्षक के बारे में सोच सकता है, तो कृपया वर्तमान को संशोधित करें।

अद्यतन: कुछ और सोचने के बाद, अब मुझे आश्वस्त है कि विनाशक को तीन बार बुलाया जा सकता है। अतिरिक्त एक कॉलिंग फ़ंक्शन void test() में अस्थायी ऑब्जेक्ट किए गए ऑटो-जेनरेट के लिए act1 की प्रतिलिपि बनाने के लिए तर्क के रूप में उपयोग किया जाता है। इसे g ++ में -fno-elide-constructors विकल्प के साथ सत्यापित किया जा सकता है। जिन लोगों के पास मेरा वही प्रश्न है, उनके लिए बिल लिंच द्वारा दिए गए जवाब में Copy elision के साथ-साथ Return value optimization देखें।

+2

प्रति प्रश्न एक प्रश्न, कृपया। –

+1

re # 2 मुझे नहीं लगता कि क्यों नहीं। मुझे लगता है कि किसी को 'ऑटो' विचार से भ्रमित किया गया था, यह पढ़ना आसान था। –

+7

@ लाइटनेसरेसेसिन ऑर्बिट टेम्पलेट तर्क कटौती। आप वास्तव में लैम्ब्डा की वजह से 'Final_action 'के प्रकार को नहीं लिख सकते हैं। – Quentin

उत्तर

-5

इसे एक बार कहा जाता है कि act1 चर अब दायरे में नहीं है। यह RAII का एक उदाहरण है, जो सी ++ में एक आम मुहावरे है।

+1

लेकिन 'आखिरकार' में अस्थायी वस्तु के साथ क्या होता है, यह क्यों नष्ट नहीं होगा? – btshengsheng

11

आप सही हैं, यह कोड टूटा हुआ है। यह केवल सही ढंग से काम करता है जब return value optimizations लागू होते हैं। यह पंक्ति:

auto act1 = finally([&]{delete p;cout<<"Goodbye,cruel world\n"}) 

कॉपी कन्स्ट्रक्टर का आह्वान या मई नहीं हो सकता है। यदि ऐसा होता है, तो आपके पास Final_action प्रकार के दो ऑब्जेक्ट होंगे और आप इस प्रकार लैम्ब्डा को दो बार कॉल करेंगे।

+0

शायद कोई कहता है "ठीक है यह है कि यह _my_ सिस्टम पर कैसे काम करता है" और दूरस्थ रूप से पोर्टेबल कोड लिखने के लिए पर्याप्त देखभाल नहीं करता .. या यहां तक ​​कि कोड जो ठीक से विफल रहता है :( –

+0

नोट: इसे ठीक करने के लिए, 'Final_action' केवल एक movable टाइप करें। – Quentin

+1

@ क्वांटिन: यहां तक ​​कि आसान फिक्स: 'auto &&' - 'act1' से मिलान करने के लिए अस्थायी जीवनकाल को बढ़ाता है, –

2

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

एक अपेक्षाकृत आसान और पोर्टेबल समाधान नहीं है:

bool valid = true;Final_action करने के लिए एक ध्वज जोड़ें और ऊपर लिख ले जाने c'tor और स्रोत ऑब्जेक्ट में झंडा स्पष्ट करने के काम चलते हैं। valid पर केवल clean() का आह्वान करें।यह कॉपी c'tor की पीढ़ी को रोकता है और प्रतिलिपि प्रतिलिपि बनाता है, इसलिए उन्हें स्पष्ट रूप से हटाए जाने की आवश्यकता नहीं है। (बोनस पॉइंट्स: ध्वज को एक पुन: प्रयोज्य चाल-केवल रैपर में रखें ताकि आपको चालक को लागू करने की आवश्यकता न हो और Final_action के असाइनमेंट को स्थानांतरित न करें। आपको इस मामले में स्पष्ट हटाए जाने की आवश्यकता नहीं है।)

वैकल्पिक रूप से, Final_action के टेम्पलेट तर्क को हटाएं और इसके बजाय std::function<void()> का उपयोग करने के लिए इसे बदलें। जांचें कि clean इसे आमंत्रित करने से पहले खाली नहीं है। Move c'tor जोड़ें और असाइनमेंट को ले जाएं जो मूल std::function से nullptr पर सेट करें। (हाँ, यह पोर्टेबल होना जरूरी है। std::function गारंटी देता है कि स्रोत खाली होगा।) लाभ: टाइप एरर के सामान्य लाभ, जैसे स्कोप गार्ड को बाहरी स्टैक फ्रेम में वापस करने में सक्षम होना F का खुलासा। नुकसान: महत्वपूर्ण रन टाइम ओवरहेड जोड़ सकते हैं।

मेरी वर्तमान कार्य प्रोजेक्ट में, मैंने मूल रूप से ScopeGuard<F> और AnyScopeGuard के साथ दो दृष्टिकोणों को संयुक्त प्रकार के फ़ंक्शन ऑब्जेक्ट का उपयोग करके संयुक्त किया। पूर्व boost::optional<F> का उपयोग करता है और बाद में परिवर्तित किया जा सकता है। स्कोप गार्ड को खाली होने की अनुमति देने के एक अतिरिक्त लाभ के रूप में, मैं स्पष्ट रूप से dismiss() भी कर सकता हूं। यह एक लेनदेन के रोलबैक हिस्से को स्थापित करने के लिए स्कोप गार्ड का उपयोग करने और फिर इसे प्रतिबद्ध करने (गैर-फेंकने कोड के साथ) को खारिज करने की अनुमति देता है।

अद्यतन: स्ट्रॉस्ट्रप का नया उदाहरण संकलित भी नहीं करता है। मुझे याद आया कि कॉपी c'tor को स्पष्ट रूप से हटाने से चालक की पीढ़ी को भी अक्षम कर दिया जाता है।

+0

साइमन क्राइमर द्वारा संशोधित कोड ** वास्तव में ** संकलित नहीं होगा (मैंने g ++ के साथ परीक्षण किया है), क्योंकि जैसा कि आपने कहा था, कन्स्ट्रक्टर को स्पष्ट रूप से घोषित (हटाए गए अनुसार) के रूप में कन्स्ट्रक्टर ऑटो-जनरेट नहीं किया जाएगा, देखें [यहां] (https://msdn.microsoft.com/en-us/library/dn457344.aspx)। – btshengsheng

+1

अपना पहला पैराग्राफ 6 बार पढ़ने के बाद, मुझे लगता है कि आखिरकार मैं आपका मतलब समझता हूं :-)। मुझे लगता है कि आप अपने उत्तर की चौथी पंक्ति पर _neither_ चूक गए हैं? बीटीडब्लू, स्ट्रॉस्ट्रप को बार-बार गलत कोड बनाने का देखना दिलचस्प है ... धन्यवाद। – btshengsheng

2

सरल ठीक

template<typename F> 
struct Final_action 
{ 
    Final_action(F f): clean{std::move(f)} {} 
    Final_action(const Final_action&) = delete; 
    void operator=(const Final_action&) = delete; 
    ~Final_action() { clean(); } 
    F clean; 
}; 

template<class F> 
Final_action<F> finally(F f) 
{ 
    return { std::move(f) }; 
} 

और जैसा कि

auto&& act1 = finally([&]{delete p;cout<<"Goodbye,cruel world\n";}); 

कॉपी-सूची-आरंभीकरण और एक जीवन भर-विस्तार अग्रेषण संदर्भ के उपयोग का उपयोग/किसी भी प्रतिलिपि से बचने Final_action वस्तु की चलती है। कॉपी-लिस्ट-प्रारंभिक अस्थायी Final_action रिटर्न वैल्यू सीधे बनाता है, और finally द्वारा अस्थायी रूप से लौटाया गया है, इसका जीवनकाल act1 पर बाध्यकारी है - बिना किसी प्रतिलिपि या चलने के।

+0

ठीक काम करता है! जो लोग सोच सकते हैं, वापसी बयान में घुंघराले ब्रेस 'वापसी {std :: move (f)} 'प्रतिलिपि-सूची-प्रारंभिकता को आमंत्रित करता है, [यहां] देखें (http://en.cppreference.com/w/ सीपीपी/भाषा/वापसी)। – btshengsheng

संबंधित मुद्दे