सवाल कॉपी-एंड-स्वैप मुहावरे क्या है?


यह मुहावरे क्या है और इसका उपयोग कब किया जाना चाहिए? यह कौन सी समस्याएं हल करती है? क्या सी ++ 11 का उपयोग होने पर मुहावरे बदल जाता है?

यद्यपि इसका उल्लेख कई स्थानों पर किया गया है, लेकिन हमारे पास कोई एकवचन "यह क्या है" प्रश्न और उत्तर नहीं था, इसलिए यहां यह है। यहां उन स्थानों की आंशिक सूची दी गई है जहां पहले उल्लेख किया गया था:


1671
2017-07-19 08:42


मूल


gotw.ca/gotw/059.htm हर्ब सटर से - DumbCoder
बहुत बढ़िया, मैंने इस सवाल को मुझसे जोड़ा अर्थशास्त्र को स्थानांतरित करने का उत्तर। - fredoverflow
इस मुहावरे के लिए पूरी तरह से स्पष्टीकरण रखने का अच्छा विचार है, यह इतना आम है कि हर किसी को इसके बारे में पता होना चाहिए। - Matthieu M.
चेतावनी: कॉपी / स्वैप मुहावरे उपयोगी होने की तुलना में कहीं अधिक बार प्रयोग किया जाता है। कॉपी असाइनमेंट से एक मजबूत अपवाद सुरक्षा गारंटी की आवश्यकता नहीं होने पर यह प्रदर्शन के लिए अक्सर हानिकारक होता है। और जब कॉपी असाइनमेंट के लिए मजबूत अपवाद सुरक्षा की आवश्यकता होती है, तो इसे एक बहुत ही सामान्य प्रतिलिपि ऑपरेटर के अलावा, एक सामान्य जेनेरिक फ़ंक्शन द्वारा आसानी से प्रदान किया जाता है। देख slideshare.net/ripplelabs/howard-hinnant-accu2014 स्लाइड्स 43 - 53. सारांश: टूलबॉक्स में कॉपी / स्वैप एक उपयोगी टूल है। लेकिन यह अधिक विपणन किया गया है और बाद में अक्सर दुर्व्यवहार किया गया है। - Howard Hinnant
@ हावर्ड हिन्नेंट: हाँ, उस पर +1। मैंने इसे एक समय में लिखा था जहां लगभग हर सी ++ सवाल था "जब मेरी प्रतिलिपि में मेरी कक्षा दुर्घटनाग्रस्त हो जाती है" और यह मेरी प्रतिक्रिया थी। यह उचित है जब आप सिर्फ काम करने की प्रतिलिपि बनाना चाहते हैं- / move-semantics या जो कुछ भी आप अन्य चीजों पर जा सकते हैं, लेकिन यह वास्तव में इष्टतम नहीं है। अगर आपको लगता है कि मदद मिलेगी तो मेरे उत्तर के शीर्ष पर अस्वीकरण करने के लिए स्वतंत्र महसूस करें। - GManNickG


जवाब:


अवलोकन

हमें कॉपी-एंड-स्वैप मुहावरे की आवश्यकता क्यों है?

कोई भी वर्ग जो संसाधन प्रबंधित करता है (ए आवरण, एक स्मार्ट सूचक की तरह) को लागू करने की जरूरत है द बिग थ्री। जबकि कॉपी-कन्स्ट्रक्टर और विनाशक के लक्ष्य और कार्यान्वयन सरल हैं, प्रतिलिपि असाइनमेंट ऑपरेटर तर्कसंगत रूप से सबसे अधिक ज्ञात और कठिन है। यह कैसे किया जाना चाहिए? क्या नुकसान से बचा जाना चाहिए?

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

यह कैसे काम करता है?

सैद्धांतिक रूप, यह डेटा की स्थानीय प्रतिलिपि बनाने के लिए कॉपी-कन्स्ट्रक्टर की कार्यक्षमता का उपयोग करके काम करता है, फिर प्रतिलिपि डेटा को ए के साथ ले जाता है swap नए डेटा के साथ पुराने डेटा को स्वैप करना। अस्थायी प्रतिलिपि तब नष्ट हो जाती है, इसके साथ पुराना डेटा लेता है। हमें नए डेटा की एक प्रति के साथ छोड़ दिया गया है।

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

एक स्वैप समारोह एक है गैर फेंक फ़ंक्शन जो वर्ग के दो ऑब्जेक्ट्स को सदस्य बनाता है, सदस्य के लिए सदस्य। हम उपयोग करने के लिए प्रलोभन हो सकता है std::swap अपना खुद का उपलब्ध कराने के बजाय, लेकिन यह असंभव होगा; std::swap इसके कार्यान्वयन के भीतर कॉपी-कन्स्ट्रक्टर और कॉपी-असाइनमेंट ऑपरेटर का उपयोग करता है, और अंत में हम असाइनमेंट ऑपरेटर को स्वयं के संदर्भ में परिभाषित करने का प्रयास करेंगे!

(न केवल वह, लेकिन अयोग्य कॉल करने के लिए swap अनावश्यक निर्माण और हमारी कक्षा के विनाश पर छोड़कर, हमारे कस्टम स्वैप ऑपरेटर का उपयोग करेंगे std::swap लागू होगा।)


एक गहराई से स्पष्टीकरण

लक्ष्य

चलो एक ठोस मामला मानते हैं। हम एक अन्यथा बेकार वर्ग, एक गतिशील सरणी में, प्रबंधन करना चाहते हैं। हम एक कामकाजी कन्स्ट्रक्टर, प्रति-निर्माता, और विनाशक के साथ शुरू करते हैं:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr),
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

यह वर्ग लगभग सरणी को सफलतापूर्वक प्रबंधित करता है, लेकिन इसकी आवश्यकता है operator= सही ढंग से काम करने के लिए।

एक असफल समाधान

यहां बताया गया है कि एक निष्पक्ष कार्यान्वयन कैसे देख सकता है:

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = nullptr; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : nullptr; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
}

और हम कहते हैं कि हम समाप्त हो गए हैं; यह अब लीक के बिना, एक सरणी का प्रबंधन करता है। हालांकि, यह तीन समस्याओं से ग्रस्त है, जो अनुक्रमिक रूप से कोड में चिह्नित हैं (n)

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

  2. दूसरा यह है कि यह केवल एक बुनियादी अपवाद गारंटी प्रदान करता है। अगर new int[mSize] विफल रहता है, *this संशोधित किया जाएगा। (अर्थात्, आकार गलत है और डेटा चला गया है!) एक मजबूत अपवाद गारंटी के लिए, इसे कुछ ऐसा होना चाहिए:

    dumb_array& operator=(const dumb_array& other)
    {
        if (this != &other) // (1)
        {
            // get the new data ready before we replace the old
            std::size_t newSize = other.mSize;
            int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
            std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
    
            // replace the old data (all are non-throwing)
            delete [] mArray;
            mSize = newSize;
            mArray = newArray;
        }
    
        return *this;
    }
    
  3. कोड विस्तारित हो गया है! जो हमें तीसरी समस्या का कारण बनता है: कोड डुप्लिकेशन। हमारा असाइनमेंट ऑपरेटर प्रभावी रूप से अन्य कोड को डुप्लिकेट करता है जिसे हमने पहले ही लिखा है, और यह एक भयानक बात है।

हमारे मामले में, इसका मूल केवल दो पंक्तियां (आवंटन और प्रतिलिपि) है, लेकिन अधिक जटिल संसाधनों के साथ यह कोड ब्लोट काफी परेशानी हो सकती है। हमें खुद को दोहराने का प्रयास नहीं करना चाहिए।

(कोई आश्चर्यचकित हो सकता है: यदि एक संसाधन को सही तरीके से प्रबंधित करने के लिए यह कोड आवश्यक है, तो क्या होगा यदि मेरी कक्षा एक से अधिक का प्रबंधन करे? हालांकि यह एक वैध चिंता प्रतीत हो सकती है, और वास्तव में इसे गैर-तुच्छ की आवश्यकता होती है try/catch खंड, यह एक गैर-मुद्दा है। ऐसा इसलिए है क्योंकि एक वर्ग को प्रबंधित करना चाहिए केवल एक संसाधन!)

एक सफल समाधान

जैसा कि बताया गया है, कॉपी-एंड-स्वैप मुहावरे इन सभी मुद्दों को ठीक करेगा। लेकिन अभी, हमारे पास एक को छोड़कर सभी आवश्यकताएं हैं: ए swap समारोह। जबकि तीन नियमों में सफलतापूर्वक हमारे प्रति-निर्माता, असाइनमेंट ऑपरेटर और विनाशक के अस्तित्व को शामिल किया गया है, इसे वास्तव में "द बिग थ्री एंड ए हाफ" कहा जाना चाहिए: जब भी आपकी कक्षा संसाधन का प्रबंधन करती है, तो यह भी समझ में आता है कि यह भी प्रदान करता है swap समारोह।

हमें अपनी कक्षा में स्वैप कार्यक्षमता जोड़ने की जरूरत है, और हम निम्नानुसार करते हैं †:

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;

        // by swapping the members of two objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

(यहाँ स्पष्टीकरण क्यों है public friend swap।) अब न केवल हम अपने स्वैप कर सकते हैं dumb_arrayहै, लेकिन सामान्य रूप से स्वैप अधिक कुशल हो सकता है; यह केवल पूरे सरणी आवंटित करने और प्रतिलिपि बनाने के बजाय पॉइंटर्स और आकार को स्वैप करता है। कार्यक्षमता और दक्षता में इस बोनस के अलावा, अब हम कॉपी-एंड-स्वैप मुहावरे को लागू करने के लिए तैयार हैं।

आगे के बिना, हमारे असाइनमेंट ऑपरेटर है:

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
}

और बस! एक के साथ झुकाव के साथ, सभी तीन समस्याओं को एक बार में सुंदर ढंग से निपटाया जाता है।

यह क्यों काम करता है?

हम पहले एक महत्वपूर्ण विकल्प देखते हैं: पैरामीटर तर्क लिया जाता है दर-मूल्य। जबकि कोई भी आसानी से निम्नलिखित कर सकता है (और वास्तव में, मुहावरे के कई बेवकूफ कार्यान्वयन):

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

हम एक खो देते हैं महत्वपूर्ण अनुकूलन अवसर। इतना ही नहीं, लेकिन यह विकल्प सी ++ 11 में महत्वपूर्ण है, जिस पर बाद में चर्चा की गई है। (एक सामान्य नोट पर, एक उल्लेखनीय उपयोगी दिशानिर्देश निम्नानुसार है: यदि आप किसी फ़ंक्शन में किसी चीज़ की प्रतिलिपि बनाने जा रहे हैं, तो संकलक को पैरामीटर सूची में करने दें। ‡)

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

निरीक्षण करें कि फ़ंक्शन में प्रवेश करने पर सभी नए डेटा पहले ही आवंटित किए गए हैं, कॉपी किए गए हैं और उपयोग किए जाने के लिए तैयार हैं। यही वह है जो हमें मुफ्त में एक मजबूत अपवाद गारंटी देता है: यदि प्रतिलिपि का निर्माण विफल रहता है तो हम फ़ंक्शन में भी प्रवेश नहीं करेंगे, और इसलिए राज्य को बदलना संभव नहीं है *this। (हमने मजबूत अपवाद गारंटी के लिए मैन्युअल रूप से पहले क्या किया था, संकलक अब हमारे लिए कर रहा है; कितना दयालु।)

इस बिंदु पर हम घर मुक्त हैं, क्योंकि swap गैर फेंक रहा है। हम अपने वर्तमान डेटा को कॉपी किए गए डेटा के साथ स्वैप करते हैं, सुरक्षित रूप से हमारे राज्य को बदलते हैं, और पुराना डेटा अस्थायी में डाल दिया जाता है। जब समारोह वापस आता है तो पुराना डेटा तब जारी किया जाता है। (जहां पैरामीटर के दायरे समाप्त होते हैं और इसके विनाशक को बुलाया जाता है।)

चूंकि मुहावरे कोई कोड दोहराता है, हम ऑपरेटर के भीतर कीड़े पेश नहीं कर सकते हैं। ध्यान दें कि इसका मतलब है कि हम एक स्व-असाइनमेंट चेक की आवश्यकता से छुटकारा पा रहे हैं, जिससे एक समान वर्दी कार्यान्वयन की अनुमति मिलती है operator=। (इसके अतिरिक्त, अब हमारे पास गैर-कार्य-कार्य पर प्रदर्शन दंड नहीं है।)

और वह कॉपी-एंड-स्वैप मुहावरे है।

सी ++ 11 के बारे में क्या?

सी ++, सी ++ 11 का अगला संस्करण, संसाधनों को प्रबंधित करने के तरीके में एक बहुत ही महत्वपूर्ण परिवर्तन करता है: तीन का नियम अब है चार का नियम (और आधा)। क्यूं कर? क्योंकि न केवल हमें अपने संसाधन की प्रतिलिपि बनाने में सक्षम होने की आवश्यकता है, हमें इसे भी स्थानांतरित करने की आवश्यकता है

सौभाग्य से हमारे लिए, यह आसान है:

class dumb_array
{
public:
    // ...

    // move constructor
    dumb_array(dumb_array&& other)
        : dumb_array() // initialize via default constructor, C++11 only
    {
        swap(*this, other);
    }

    // ...
};

यहाँ क्या चल रहा है? चाल-निर्माण के लक्ष्य को याद करें: कक्षा के दूसरे उदाहरण से संसाधनों को लेने के लिए, इसे एक राज्य में छोड़कर असाइन करने योग्य और विनाशकारी होने की गारंटी दी जाती है।

तो हमने जो किया है वह सरल है: डिफ़ॉल्ट कन्स्ट्रक्टर (एक सी ++ 11 फीचर) के माध्यम से प्रारंभ करें, फिर साथ स्वैप करें other; हम जानते हैं कि हमारी कक्षा का एक डिफ़ॉल्ट निर्मित उदाहरण सुरक्षित रूप से असाइन किया जा सकता है और नष्ट कर दिया जा सकता है, इसलिए हम जानते हैं other स्वैपिंग के बाद, वही करने में सक्षम हो जाएगा।

(ध्यान दें कि कुछ कंपाइलर कन्स्ट्रक्टर प्रतिनिधिमंडल का समर्थन नहीं करते हैं; इस मामले में, हमें कक्षा को मैन्युअल रूप से डिफॉल्ट करना होगा। यह एक दुर्भाग्यपूर्ण लेकिन सौभाग्यपूर्ण मामूली कार्य है।)

वह क्यों काम करता है?

यही एकमात्र बदलाव है जिसे हमें अपनी कक्षा में बनाने की ज़रूरत है, तो यह क्यों काम करता है? पैरामीटर को एक मान बनाने के लिए किए गए हमेशा के लिए महत्वपूर्ण निर्णय याद रखें, संदर्भ नहीं:

dumb_array& operator=(dumb_array other); // (1)

अब अगर other एक रावल्यू के साथ शुरू किया जा रहा है, इसे स्थानांतरित किया जाएगा। उत्तम। उसी तरह सी ++ 03 हमें तर्क-मूल्य लेने के द्वारा हमारी कॉपी-कन्स्ट्रक्टर कार्यक्षमता का फिर से उपयोग करने दें, सी ++ 11 होगा खुद ब खुद उचित होने पर चालक-कन्स्ट्रक्टर को भी चुनें। (और, ज़ाहिर है, जैसा कि पहले से जुड़े आलेख में उल्लिखित है, मूल्य की प्रतिलिपि / गति को पूरी तरह से elided किया जा सकता है।)

और इसलिए कॉपी-एंड-स्वैप मुहावरे का निष्कर्ष निकाला जाता है।


फुटनोट

* हम क्यों सेट करते हैं mArray शून्य करने के लिए? क्योंकि अगर ऑपरेटर में कोई और कोड फेंकता है, तो विनाशक dumb_array कहा जा सकता है; और यदि यह शून्य के बिना सेट किए बिना होता है, तो हम उस स्मृति को हटाने का प्रयास करते हैं जो पहले ही हटा दिया गया है! हम इसे शून्य से सेट करके इससे बचते हैं, क्योंकि शून्य हटाने से कोई ऑपरेशन नहीं होता है।

† अन्य दावे भी हैं जिन्हें हमें विशेषज्ञ बनाना चाहिए std::swap हमारे प्रकार के लिए, एक कक्षा प्रदान करें swap साथ-साथ एक फ्री-फ़ंक्शन swap, आदि। लेकिन यह सब अनावश्यक है: इसका कोई उचित उपयोग swap एक अयोग्य कॉल के माध्यम से होगा, और हमारे समारोह के माध्यम से मिलेगा ADL। एक समारोह करेगा।

‡ कारण सरल है: एक बार जब आपके पास संसाधन हो, तो आप इसे कहीं भी स्वैप कर सकते हैं और / या इसे स्थानांतरित कर सकते हैं (सी ++ 11) कहीं भी इसकी आवश्यकता होती है। और पैरामीटर सूची में प्रतिलिपि बनाकर, आप अनुकूलन को अधिकतम करते हैं।


1841
2017-07-19 08:43



@GMan: मैं तर्क दूंगा कि एक बार में कई संसाधनों का प्रबंधन करने वाली कक्षा विफल होने के लिए बर्बाद हो जाती है (अपवाद सुरक्षा रात्रिभोज हो जाती है) और मैं दृढ़ता से अनुशंसा करता हूं कि कोई वर्ग एक संसाधन का प्रबंधन करे या इसमें व्यावसायिक कार्यक्षमता हो और प्रबंधकों का उपयोग करें। - Matthieu M.
@FrEEzE: "यह संकलक विशिष्ट है जिसमें सूची संसाधित की जाती है।" नहीं यह नहीं। यह कक्षा परिभाषा में दिखाई देने के क्रम में संसाधित किया जाता है। एक संकलक जो स्वीकार नहीं करता है std::copy इस तरह टूटा हुआ है, मैं टूटे हुए कंपाइलरों के लिए कोडिंग नहीं कर रहा हूं। और मुझे यकीन नहीं है कि मैं आपकी आखिरी टिप्पणी को समझता हूं। - GManNickG
@Freeze: इसके अलावा, इस उत्तर का बिंदु एक सी ++ मुहावरे के बारे में बात करना है। यदि आपको गैर-अनुपालन कंपाइलर के साथ काम करने के लिए अपने प्रोग्राम को हैक करना है, तो यह ठीक है, लेकिन यह मेरी ज़िम्मेदारी की तरह कार्य करने की कोशिश न करें या यह "बुरा अभ्यास" है, कृपया। - GManNickG
मुझे नहीं लगता कि स्वैप विधि को मित्र के रूप में क्यों घोषित किया गया है? - szx
@neuviemeporte: आपको अपनी ज़रूरत है swap एडीएल के दौरान पाया जा सकता है यदि आप इसे सबसे सामान्य कोड में काम करना चाहते हैं, जैसे आप पार करेंगे boost::swap और अन्य विभिन्न स्वैप उदाहरण। स्वैप सी ++ में एक मुश्किल मुद्दा है, और आम तौर पर हम सभी सहमत हैं कि पहुंच का एक बिंदु सर्वोत्तम (स्थिरता के लिए) है, और सामान्य रूप से ऐसा करने का एकमात्र तरीका एक नि: शुल्क कार्य है (int उदाहरण के लिए, एक स्वैप सदस्य नहीं हो सकता है)। देख मेरा प्रश्न कुछ पृष्ठभूमि के लिए। - GManNickG


असाइनमेंट, उसके दिल में, दो कदम हैं: वस्तु के पुराने राज्य को फाड़ना तथा एक प्रति के रूप में अपने नए राज्य का निर्माण किसी अन्य वस्तु के राज्य का।

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

अपने परिष्कृत रूप में, प्रतिलिपि ऑपरेटर के (गैर-संदर्भ) पैरामीटर को प्रारंभ करके प्रतिलिपि बनाई गई प्रतिलिपि बनाकर कॉपी-एंड-स्वैप लागू किया जाता है:

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}

227
2017-07-19 08:55



मुझे लगता है कि पिंपल का उल्लेख प्रतिलिपि, स्वैप और विनाश का जिक्र करना उतना ही महत्वपूर्ण है। स्वैप जादुई रूप से अपवाद-सुरक्षित नहीं है। यह अपवाद-सुरक्षित है क्योंकि स्वैपिंग पॉइंटर्स अपवाद-सुरक्षित है। आप नहीं करते है एक पिंपल का उपयोग करने के लिए, लेकिन यदि आप नहीं करते हैं तो आपको यह सुनिश्चित करना होगा कि किसी सदस्य का प्रत्येक स्वैप अपवाद-सुरक्षित है। यह एक दुःस्वप्न हो सकता है जब ये सदस्य बदल सकते हैं और जब वे एक पिंपल के पीछे छिपे होते हैं तो यह छोटा होता है। और फिर, पिंपल की लागत आता है। जो हमें इस निष्कर्ष पर ले जाता है कि अक्सर अपवाद-सुरक्षा प्रदर्शन में लागत लाती है। - wilhelmtell
std::swap(this_string, that) नो-थ्रो गारंटी प्रदान नहीं करता है। यह मजबूत अपवाद सुरक्षा प्रदान करता है, लेकिन नो-थ्रो गारंटी नहीं। - wilhelmtell
@ विल्हेल्मटेल: सी ++ 03 में, संभावित रूप से फेंकने वाले अपवादों का कोई उल्लेख नहीं है std::string::swap (जिसे द्वारा बुलाया जाता है std::swap)। सी ++ 0x में, std::string::swap है noexcept और अपवाद फेंकना नहीं चाहिए। - James McNellis
@sbi @JamesMcNellis ठीक है, लेकिन बिंदु अभी भी खड़ा है: यदि आपके पास कक्षा के प्रकार के सदस्य हैं तो आपको यह सुनिश्चित करना होगा कि उन्हें स्वैप करना कोई फेंकना न हो। यदि आपके पास एक सदस्य है जो एक सूचक है तो यह छोटा है। अन्यथा यह नहीं है। - wilhelmtell
@ विल्हेल्मटेल: मैंने सोचा था कि स्वैपिंग का मुद्दा था: यह कभी नहीं फेंकता और यह हमेशा ओ (1) (हाँ, मुझे पता है, std::array...) - sbi


पहले से ही कुछ अच्छे जवाब हैं। मैं ध्यान केंद्रित करूंगा में मुख्य मुझे लगता है कि उनकी कमी है - प्रतिलिपि और स्वैप मुहावरे के साथ "विपक्ष" का एक स्पष्टीकरण ....

कॉपी-एंड-स्वैप मुहावरे क्या है?

स्वैप फ़ंक्शन के संदर्भ में असाइनमेंट ऑपरेटर को कार्यान्वित करने का एक तरीका:

X& operator=(X rhs)
{
    swap(rhs);
    return *this;
}

मौलिक विचार यह है कि:

  • ऑब्जेक्ट को असाइन करने का सबसे त्रुटि-प्रवण हिस्सा यह सुनिश्चित करना है कि नए राज्य की जरूरतों को हासिल करने वाले किसी भी संसाधन को सुनिश्चित किया जाए (उदा। स्मृति, वर्णनकर्ता)

  • कि अधिग्रहण का प्रयास किया जा सकता है से पहले वस्तु की वर्तमान स्थिति को संशोधित करना (यानी। *this) यदि नए मूल्य की एक प्रति बनाई गई है, तो यही कारण है कि rhs स्वीकार कर लिया है मूल्य से (यानी कॉपी किया गया) के बजाए संदर्भ से

  • स्थानीय प्रतिलिपि की स्थिति स्वैपिंग rhs तथा *this है आमतौर पर संभावित विफलता / अपवादों के बिना अपेक्षाकृत आसान है, स्थानीय प्रति को बाद में किसी विशेष राज्य की आवश्यकता नहीं होती है (केवल विनाशक के लिए राज्य फिट की आवश्यकता होती है, जितना कि किसी ऑब्जेक्ट के लिए ले जाया गया से = = सी ++ 11)

इसका इस्तेमाल कब किया जाना चाहिए? (यह कौन सी समस्याएं हल करती है [/सर्जन करना]?)

  • जब आप एक असाइनमेंट द्वारा असाइन किए गए असाइन किए गए ऑब्जेक्ट को अपवादित करना चाहते हैं, तो मान लें कि आपके पास एक है या लिख ​​सकता है swap मजबूत अपवाद गारंटी के साथ, और आदर्श रूप से जो विफल नहीं हो सकता /throw.. †

  • जब आप एक साफ, समझने में आसान, असाइनमेंट ऑपरेटर को परिभाषित करने के लिए मजबूत तरीका (सरल) कॉपी कन्स्ट्रक्टर के संदर्भ में, swap और विनाशक कार्यों।

    • कॉपी-एंड-स्वैप के रूप में स्वयं-असाइनमेंट किए गए किनारे के मामलों से बचा जाता है। ‡

  • असाइनमेंट के दौरान अतिरिक्त अस्थायी ऑब्जेक्ट द्वारा बनाए गए किसी प्रदर्शन प्रदर्शन या क्षणिक रूप से उच्च संसाधन उपयोग आपके आवेदन के लिए महत्वपूर्ण नहीं है। ⁂

swap फेंकना: आमतौर पर डेटा सदस्यों को विश्वसनीय रूप से स्वैप करना संभव है कि ऑब्जेक्ट पॉइंटर द्वारा ट्रैक किए जाते हैं, लेकिन गैर-पॉइंटर डेटा सदस्यों जिनके पास फेंक-फ्री स्वैप नहीं है, या जिसके लिए स्वैपिंग को लागू किया जाना है X tmp = lhs; lhs = rhs; rhs = tmp; और प्रति-निर्माण या असाइनमेंट फेंक सकता है, फिर भी कुछ डेटा सदस्यों को स्वैप करने में विफल होने की संभावना है और अन्य नहीं। यह क्षमता सी ++ 03 तक भी लागू होती है std::stringजैसा कि जेम्स ने एक और जवाब पर टिप्पणी की है:

@ विल्हेल्मटेल: सी ++ 03 में, std :: string :: swap (जिसे std :: swap द्वारा बुलाया जाता है) द्वारा संभावित रूप से फेंकने वाले अपवादों का कोई उल्लेख नहीं है। सी ++ 0x में, std :: string :: स्वैप अस्वीकार्य है और अपवाद फेंकना नहीं चाहिए। - जेम्स मैकनेलिस 22 दिसंबर 1010 को 15:24 बजे


‡ असाइनमेंट ऑपरेटर कार्यान्वयन जो किसी विशिष्ट ऑब्जेक्ट से असाइन करते समय सायन लगता है, वह स्वयं-असाइनमेंट के लिए आसानी से विफल हो सकता है। हालांकि यह अकल्पनीय प्रतीत हो सकता है कि क्लाइंट कोड स्वयं-असाइनमेंट का भी प्रयास करेगा, यह कंटेनरों पर अलगो संचालन के दौरान अपेक्षाकृत आसानी से हो सकता है x = f(x); कोड कहां f है (शायद केवल कुछ के लिए #ifdef शाखाएं) एक मैक्रो अला #define f(x) x या एक संदर्भ लौटने वाला एक समारोह x, या यहां तक ​​कि (संभवतः अक्षम लेकिन संक्षिप्त) कोड जैसे x = c1 ? x * 2 : c2 ? x / 2 : x;)। उदाहरण के लिए:

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

स्व-असाइनमेंट पर, उपरोक्त कोड हटाएं x.p_;, अंक p_ एक नए आवंटित ढेर क्षेत्र में, फिर पढ़ने के लिए प्रयास करता है uninitialised उसमें डेटा (अपरिभाषित व्यवहार), अगर वह कुछ भी अजीब नहीं करता है, copy हर बस विनाशकारी 'टी' के लिए एक आत्म-असाइनमेंट का प्रयास करता है!


⁂ प्रति-और-स्वैप मुहावरे अतिरिक्त अस्थायी (जब ऑपरेटर का पैरामीटर प्रति-निर्मित होता है) के उपयोग के कारण अक्षमता या सीमाएं पेश कर सकता है:

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

यहां, एक हाथ से लिखित Client::operator= जांच सकते हैं कि क्या *this पहले से ही उसी सर्वर से जुड़ा हुआ है rhs (शायद उपयोगी होने पर "रीसेट" कोड भेजना), जबकि कॉपी-एंड-स्वैप दृष्टिकोण कॉपी-कन्स्ट्रक्टर का आह्वान करेगा जो संभवतः एक अलग सॉकेट कनेक्शन खोलने के लिए लिखा जाएगा, फिर मूल को बंद करें। न केवल एक साधारण इन-प्रोसेस वेरिएबल प्रतिलिपि के बजाय रिमोट नेटवर्क इंटरैक्शन का मतलब हो सकता है, यह सॉकेट संसाधनों या कनेक्शन पर क्लाइंट या सर्वर सीमाओं को दूर कर सकता है। (बेशक इस वर्ग में एक बहुत ही भयंकर इंटरफ़ेस है, लेकिन यह एक और मामला है; -पी)।


32
2018-03-06 14:51



उस ने कहा, एक सॉकेट कनेक्शन सिर्फ एक उदाहरण था - एक ही सिद्धांत किसी भी संभावित महंगी प्रारंभिकरण पर लागू होता है, जैसे हार्डवेयर प्रोबिंग / प्रारंभिक / अंशांकन, थ्रेड या यादृच्छिक संख्याओं का पूल उत्पन्न करना, कुछ क्रिप्टोग्राफी कार्य, कैश, फ़ाइल सिस्टम स्कैन, डेटाबेस कनेक्शन आदि .. - Tony Delroy
एक और (विशाल) con है। वर्तमान चश्मा के रूप में तकनीकी रूप से वस्तु होगी एक चाल-असाइनमेंट ऑपरेटर नहीं है! यदि बाद में कक्षा के सदस्य के रूप में उपयोग किया जाता है, तो नई कक्षा ऑटो-जेनरेट नहीं होगा! स्रोत: youtu.be/mYrbivnruYw?t=43m14s - user362515
प्रतिलिपि असाइनमेंट ऑपरेटर के साथ मुख्य समस्या Client क्या असाइनमेंट प्रतिबंधित नहीं है। - sbi


यह उत्तर उपरोक्त उत्तरों के लिए एक अतिरिक्त और मामूली संशोधन की तरह है।

विजुअल स्टूडियो (और संभवतः अन्य कंपाइलर्स) के कुछ संस्करणों में एक बग है जो वास्तव में परेशान है और समझ में नहीं आता है। तो यदि आप अपना घोषित / परिभाषित करते हैं swap इस तरह काम करें:

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... जब आप कॉल करते हैं तो संकलक आपको चिल्लाएगा swap समारोह:

enter image description here

इसके साथ कुछ करने के लिए है friend समारोह कहा जाता है और this एक पैरामीटर के रूप में पारित वस्तु।


इसका उपयोग करने के लिए एक रास्ता नहीं है friend कीवर्ड और फिर से परिभाषित करें swap समारोह:

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

इस बार, आप बस कॉल कर सकते हैं swap और पास otherइस प्रकार संकलक को खुश कर रहा है:

enter image description here


आखिरकार, आप नहीं करते जरुरत एक का उपयोग करने के लिए friend 2 वस्तुओं को स्वैप करने के लिए कार्य करें। यह बनाने के लिए उतना ही समझ में आता है swap एक सदस्य समारोह जिसमें एक है other एक पैरामीटर के रूप में वस्तु।

आपके पास पहले से ही पहुंच है this ऑब्जेक्ट, इसलिए इसे पैरामीटर के रूप में पास करना तकनीकी रूप से अनावश्यक है।


19
2017-09-04 04:50



क्या आप अपना उदाहरण साझा कर सकते हैं जो त्रुटि को पुन: उत्पन्न करता है? - GManNickG
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp  dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg। यह एक सरलीकृत संस्करण है। हर बार एक त्रुटि होती है friend समारोह के साथ बुलाया जाता है *this पैरामीटर - Oleksiy
@GManNickG यह सभी छवियों और कोड उदाहरणों के साथ एक टिप्पणी में फिट नहीं होगा। और यह ठीक है अगर लोग नीचे आते हैं, तो मुझे यकीन है कि वहां कोई है जो वही बग प्राप्त कर रहा है; इस पोस्ट की जानकारी सिर्फ वही हो सकती है जो उन्हें चाहिए। - Oleksiy
ध्यान दें कि यह आईडीई कोड हाइलाइटिंग (IntelliSense) में केवल एक बग है ... यह कोई चेतावनी / त्रुटियों के साथ ठीक ठीक संकलित करेगा। - Amro
अगर आपने पहले से ऐसा नहीं किया है तो कृपया वीएस बग की रिपोर्ट करें (और यदि यह तय नहीं किया गया है) connect.microsoft.com/VisualStudio - Matt


जब आप सी ++ 11-स्टाइल आवंटक-जागरूक कंटेनर से निपट रहे हों तो मैं चेतावनी का एक शब्द जोड़ना चाहता हूं। स्वैपिंग और असाइनमेंट में अलग-अलग अर्थशास्त्र हैं।

कंक्रीट के लिए, आइए एक कंटेनर पर विचार करें std::vector<T, A>, कहा पे A कुछ राज्य आवंटित आवंटक प्रकार है, और हम निम्नलिखित कार्यों की तुलना करेंगे:

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

दोनों कार्यों का उद्देश्य fs तथा fm देने के लिए है a राज्य कि b शुरुआत में था। हालांकि, एक छिपी सवाल है: क्या होता है a.get_allocator() != b.get_allocator()? उत्तर है, यह निर्भर करता है। चलो लिखते है AT = std::allocator_traits<A>

  • अगर AT::propagate_on_container_move_assignment है std::true_type, फिर fm के आवंटन को फिर से सौंपना a के मूल्य के साथ b.get_allocator(), अन्यथा यह नहीं करता है, और a अपने मूल आवंटक का उपयोग जारी है। उस स्थिति में, भंडारण के बाद डेटा तत्वों को व्यक्तिगत रूप से स्वैप करने की आवश्यकता है a तथा b संगत नहीं है

  • अगर AT::propagate_on_container_swap है std::true_type, फिर fs अपेक्षित फैशन में डेटा और आवंटकों दोनों को स्वैप करता है।

  • अगर AT::propagate_on_container_swap है std::false_type, तो हमें गतिशील जांच की आवश्यकता है।

    • अगर a.get_allocator() == b.get_allocator(), तो दो कंटेनर संगत भंडारण का उपयोग करते हैं, और सामान्य फैशन में आय को स्वैप करते हैं।
    • हालांकि, यदि a.get_allocator() != b.get_allocator()कार्यक्रम है अपरिभाषित व्यवहार (सीएफ। [कंटेनर .requirements.general / 8]।

अपशॉट यह है कि जैसे ही आपका कंटेनर स्टेटस आवंटकों का समर्थन करना शुरू कर देता है, उसी तरह स्वैपिंग सी ++ 11 में एक गैर-मामूली ऑपरेशन बन गया है। यह कुछ हद तक "उन्नत उपयोग केस" है, लेकिन यह पूरी तरह से असंभव नहीं है, क्योंकि आपकी कक्षा एक संसाधन प्रबंधित करने के बाद आमतौर पर चाल अनुकूलन केवल दिलचस्प हो जाती है, और स्मृति सबसे लोकप्रिय संसाधनों में से एक है।


10
2018-06-24 08:16