नकल-और-अदला-बदली क्या है?


1997

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

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


7
gotw.ca/gotw/059.htm हर्ब सटर से
DumbCoder

2
बहुत बढ़िया, मैंने इस प्रश्न को शब्दार्थ को स्थानांतरित करने के लिए अपने उत्तर से जोड़ा ।
fredoverflow

4
इस मुहावरे के लिए एक पूर्ण-व्याख्या की व्याख्या करना अच्छा है, यह इतना सामान्य है कि हर किसी को इसके बारे में पता होना चाहिए।
मैथ्यू एम।

16
चेतावनी: कॉपी / स्वैप मुहावरा का उपयोग उपयोगी होने की तुलना में कहीं अधिक बार किया जाता है। यह अक्सर प्रदर्शन के लिए हानिकारक होता है जब प्रतिलिपि असाइनमेंट से एक मजबूत अपवाद सुरक्षा गारंटी की आवश्यकता नहीं होती है। और जब कॉपी असाइनमेंट के लिए मजबूत अपवाद सुरक्षा की आवश्यकता होती है, तो यह बहुत तेजी से कॉपी असाइनमेंट ऑपरेटर के अलावा, एक छोटे सामान्य कार्य द्वारा आसानी से प्रदान किया जाता है। स्लाइडशेयर देखें ।.net /ripplelabs/ howard-hinnant-accu2014 स्लाइड 43 - 53. सारांश: कॉपी / स्वैप टूलबॉक्स में एक उपयोगी उपकरण है। लेकिन यह बहुत अधिक बाजार में आ गया है और बाद में इसका अक्सर दुरुपयोग होता है।
हावर्ड हिनान्ट

2
@ हॉवर्डहिनट: हाँ, +1 को। मैंने यह एक ऐसे समय में लिखा था, जब लगभग हर C ++ प्रश्न "कॉपी होने पर मेरी कक्षा को क्रैश होने में मदद करता था" और यह मेरी प्रतिक्रिया थी। यह उचित है जब आप सिर्फ काम की प्रतिलिपि चाहते हैं- / चाल-शब्दार्थ या जो कुछ भी आप अन्य चीजों पर आगे बढ़ सकते हैं, लेकिन यह वास्तव में इष्टतम नहीं है। यदि आपको लगता है कि मेरी मदद करेंगे, तो मेरे उत्तर के शीर्ष पर एक अस्वीकरण लगाने के लिए स्वतंत्र महसूस करें।
GMANNICKG

जवाबों:


2182

अवलोकन

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

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

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

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

वैचारिक रूप से , यह डेटा की एक स्थानीय कॉपी बनाने के लिए कॉपी-कंस्ट्रक्टर की कार्यक्षमता का उपयोग करके काम करता है, फिर कॉपी किए गए डेटा को एक 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;
}

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

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

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

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

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

और वह है नकल-और-अदला-बदली।

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

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

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

class dumb_array
{
public:
    // ...

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

    // ...
};

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

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

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

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

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

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

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

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


फुटनोट

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

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

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

Code मूव कंस्ट्रक्टर को आम तौर पर होना चाहिए noexcept, अन्यथा कुछ कोड (जैसे std::vectorतर्क का आकार बदलना) कॉपी कंस्ट्रक्टर का उपयोग तब भी करेगा जब कोई चाल समझ में आएगी। यदि कोड अंदर अपवादों को नहीं फेंकता है, तो निश्चित रूप से इसे केवल noexcept चिह्नित करें।


17
@ मन: मेरा तर्क है कि एक बार में कई संसाधनों का प्रबंधन करने वाला एक वर्ग विफल हो जाता है (अपवाद सुरक्षा बुरा सपना बन जाता है) और मैं दृढ़ता से सिफारिश करूंगा कि या तो एक वर्ग एक संसाधन का प्रबंधन करे या इसमें व्यवसाय की कार्यक्षमता हो और प्रबंधकों का उपयोग करें।
मैथ्यू एम।

22
मुझे नहीं पता कि यहाँ स्वैप विधि को मित्र के रूप में क्यों घोषित किया गया है?
szx

9
@ एसआईडी: इसे एडीएल के माध्यम से प्राप्त करने की अनुमति देने के लिए।
GManNickG

8
@neuviemeporte: कोष्ठक के साथ, ऐरे तत्व मूलभूत रूप से आरंभिक होते हैं। बिना, वे असिंचित हैं। चूंकि कॉपी कंस्ट्रक्टर में हम वैसे भी मूल्यों को अधिलेखित करेंगे, इसलिए हम आरंभीकरण को छोड़ सकते हैं।
GManNickG

10
@neuviemeporte: swapयदि आप चाहते हैं कि यह सबसे सामान्य कोड में काम करे, तो आप ADL के दौरान पा सकते हैं boost::swap। स्वैप सी + + में एक मुश्किल मुद्दा है, और आम तौर पर हम सभी इस बात से सहमत हैं कि पहुंच का एक बिंदु सबसे अच्छा है (स्थिरता के लिए), और सामान्य रूप से ऐसा करने का एकमात्र तरीका एक स्वतंत्र कार्य है ( intस्वैप सदस्य नहीं हो सकता है,) उदाहरण के लिए)। कुछ पृष्ठभूमि के लिए मेरा प्रश्न देखें ।
GManNickG

274

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

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

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

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

1
मुझे लगता है कि पिंपल का जिक्र करना उतना ही जरूरी है जितना कि कॉपी, स्वैप और विनाश का जिक्र करना। स्वैप जादुई अपवाद-सुरक्षित नहीं है। यह अपवाद-सुरक्षित है क्योंकि स्वैपिंग पॉइंट अपवाद-सुरक्षित है। आपको पिम्पल का उपयोग करने की आवश्यकता नहीं है , लेकिन यदि आप नहीं करते हैं तो आपको यह सुनिश्चित करना होगा कि सदस्य का प्रत्येक स्वैप अपवाद-सुरक्षित हो। यह एक बुरा सपना हो सकता है जब ये सदस्य बदल सकते हैं और यह एक तुच्छ के पीछे छिपे होने पर तुच्छ है। और फिर, फिर pimpl की लागत आती है। जो हमें इस निष्कर्ष की ओर ले जाता है कि अक्सर अपवाद-सुरक्षा प्रदर्शन में लागत वहन करती है।
विल्हेमटेल

7
std::swap(this_string, that)एक नो-थ्रो गारंटी प्रदान नहीं करता है। यह मजबूत अपवाद सुरक्षा प्रदान करता है, लेकिन नो-थ्रो गारंटी नहीं।
विल्हेमटेल

11
@wilhelmtell: C ++ 03 में, संभावित रूप से std::string::swap(जिसे कहा जाता है std::swap) द्वारा फेंके गए अपवादों का कोई उल्लेख नहीं है । C ++ 0x में, std::string::swapहै noexceptऔर अपवाद फेंक नहीं करना चाहिए।
जेम्स मैकनेलिस

2
@sbi @JamesMcNellis ठीक है, लेकिन बिंदु अभी भी खड़ा है: यदि आपके पास वर्ग-प्रकार के सदस्य हैं, तो आपको यह सुनिश्चित करना होगा कि उन्हें स्वैप करना एक न-फेंक है। यदि आपके पास एक एकल सदस्य है जो एक सूचक है तो यह तुच्छ है। अन्यथा यह नहीं है।
विल्हेमटेल

2
@wilhelmtell: मुझे लगा कि स्वैपिंग की बात थी: यह कभी नहीं फेंकता और यह हमेशा O (1) (हाँ, मुझे पता है, std::array...)
sbi

44

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

नकल-और-अदला-बदली क्या है?

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

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

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

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

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

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

इसका उपयोग कब किया जाना चाहिए? (किन समस्याओं का समाधान करता है [/ create] ?)

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

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

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

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

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

@wilhelmtell: C ++ 03 में, संभावित रूप से std :: string :: swap (जिसे std :: swap) कहा जाता है द्वारा फेंके गए अपवादों का कोई उल्लेख नहीं है। C ++ 0x में, std :: string :: swap noexcept है और अपवादों को नहीं फेंकना चाहिए। - जेम्स मैकनेलिस 22 दिसंबर को 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_एक नए आबंटित हीप क्षेत्र पर इंगित करता है, फिर उसमें अनइंस्टॉल किए गए डेटा को पढ़ने का प्रयास करता है (अनफाइंड बिहेवियर), यदि वह कुछ भी अजीब नहीं करता है,copy प्रत्येक के लिए एक स्व-असाइनमेंट का प्रयास करता है- नष्ट 'टी'!


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

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(शायद उपयोगी होने पर "रीसेट" कोड भेज रहा है), जबकि कॉपी-और-स्वैप दृष्टिकोण कॉपी-कंस्ट्रक्टर को आमंत्रित करेगा जो संभवतः खोलने के लिए लिखा जाएगा। एक अलग सॉकेट कनेक्शन फिर मूल एक को बंद करें। इतना ही नहीं, एक सरल-इन-प्रोसेस वैरिएबल कॉपी के बजाय एक दूरस्थ नेटवर्क इंटरैक्शन का मतलब हो सकता है, यह सॉकेट संसाधनों या कनेक्शनों पर क्लाइंट या सर्वर की सीमा से दूर चला सकता है। (बेशक इस वर्ग में एक बहुत ही भयानक इंटरफ़ेस है, लेकिन यह एक और मामला है ;-P)।


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

एक और (बड़े पैमाने पर) चोर है। वर्तमान चश्मे के रूप में तकनीकी रूप से ऑब्जेक्ट में मूव-असाइनमेंट ऑपरेटर नहीं होगा ! यदि बाद में एक वर्ग के सदस्य के रूप में उपयोग किया जाता है, तो नई कक्षा में मूव-कॉटर ऑटो-जेनरेट नहीं होगा! स्रोत: youtu.be/mYrbivnruYw?t=43m14s
user362515

3
कॉपी असाइनमेंट ऑपरेटर के साथ मुख्य समस्या Clientयह है कि असाइनमेंट निषिद्ध नहीं है।
sbi

ग्राहक उदाहरण में, कक्षा को गैर-उपयोगी बनाया जाना चाहिए।
जॉन जेड। ली

25

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

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

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

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

}

... जब आप swapफ़ंक्शन को कॉल करते हैं तो कंपाइलर आप पर चिल्लाएगा :

यहां छवि विवरण दर्ज करें

यह एक friendफ़ंक्शन के साथ कुछ करने के लिए कहा जाता है और thisऑब्जेक्ट को एक पैरामीटर के रूप में पारित किया जा रहा है।


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

void swap(A& other) {

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

}

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

यहां छवि विवरण दर्ज करें


आखिरकार, आपको 2 ऑब्जेक्ट स्वैप करने के लिए फ़ंक्शन का उपयोग करने की आवश्यकता नहीं हैfriendswapएक सदस्य फ़ंक्शन को otherपैरामीटर के रूप में एक ऑब्जेक्ट बनाने के लिए बस उतना ही समझ में आता है ।

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


1
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg । यह एक सरलीकृत संस्करण है। हर बार friendफ़ंक्शन के *thisपैरामीटर के साथ कॉल किए जाने पर एक त्रुटि प्रतीत होती है
ओलेक्सी

1
@GManNickG जैसा कि मैंने कहा, यह एक बग है और अन्य लोगों के लिए ठीक काम कर सकता है। मैं सिर्फ कुछ लोगों की मदद करना चाहता था जो मेरे समान समस्या हो सकते हैं। मैंने विजुअल स्टूडियो 2012 एक्सप्रेस और 2013 प्रीव्यू और इसे दूर करने वाली एकमात्र चीज़ दोनों के साथ यह कोशिश की, मेरा संशोधन था
ओलेक्सी

8
@GManNickG यह सभी छवियों और कोड उदाहरणों के साथ एक टिप्पणी में फिट नहीं होगा। और यह ठीक है अगर लोग नीचे जाते हैं, मुझे यकीन है कि वहाँ कोई है जो उसी बग को प्राप्त कर रहा है; इस पद की जानकारी सिर्फ वही हो सकती है जिसकी उन्हें आवश्यकता है।
ओलेक्सी

14
ध्यान दें कि यह IDE कोड हाइलाइटिंग (IntelliSense) में केवल एक बग है ... यह बिना किसी चेतावनी / त्रुटियों के ठीक ठीक संकलित करेगा।
अमरो

3
अगर आपने पहले से ऐसा नहीं किया है, तो कृपया यहाँ वी.एस. बग की सूचना दें (और अगर यह ठीक नहीं किया गया है) connect.microsoft.com/VisualStudio
मैट

15

जब आप C ++ 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(), कार्यक्रम में अपरिभाषित व्यवहार होता है (cf. [कंटेन.requirements.general / 8]।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.