तीन का नियम क्या है?


2145
  • किसी वस्तु की नकल करने का क्या मतलब है?
  • क्या हैं प्रतिलिपि निर्माता और प्रति असाइनमेंट ऑपरेटर ?
  • मुझे उन्हें खुद कब घोषित करने की आवश्यकता है?
  • मैं अपनी वस्तुओं को कॉपी होने से कैसे रोक सकता हूं?

52
कृपया पढ़ने के इस पूरे धागा और टैग विकी इससे पहले कि आप बंद करने के लिए मतदानc++-faq
भारतीय स्टेट बैंक

13
@ Binary: वोट डालने से पहले कम से कम चर्चा चर्चा पढ़ने के लिए समय निकालें । पाठ बहुत सरल हुआ करता था, लेकिन फ्रेड को इस पर विस्तार करने के लिए कहा गया था। इसके अलावा, जबकि यह व्याकरणिक रूप से चार प्रश्न हैं , यह वास्तव में इसके लिए कई पहलुओं के साथ सिर्फ एक प्रश्न है। (यदि आप इससे सहमत नहीं हैं, तो अपने प्रश्नों में से प्रत्येक का जवाब देकर अपने पीओवी को साबित करें और परिणामों पर हमें
बताएं

1
फ्रेड, यहाँ C ++ 1x के बारे में आपके जवाब के लिए एक दिलचस्प अतिरिक्त है: stackoverflow.com/questions/4782757/… । हम इससे कैसे निपटेंगे?
sbi


4
ध्यान रखें कि, C ++ 11 के रूप में, मुझे लगता है कि इसे पाँच के नियम में अपग्रेड किया गया है, या ऐसा कुछ।
paxdiablo

जवाबों:


1793

परिचय

C ++ उपयोगकर्ता-परिभाषित प्रकारों के चर को शब्दार्थ के साथ मानता है । इसका मतलब यह है कि वस्तुओं को विभिन्न संदर्भों में स्पष्ट रूप से कॉपी किया जाता है, और हमें यह समझना चाहिए कि "वस्तु की नकल" वास्तव में क्या है।

आइए एक सरल उदाहरण पर विचार करें:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(यदि आप name(name), age(age)भाग से हैरान हो गए हैं , तो इसे एक सदस्य आरंभीकरण सूची कहा जाता है ।)

विशेष सदस्य के कार्य

किसी personवस्तु की नकल करने का क्या मतलब है ? mainसमारोह दो अलग नकल परिदृश्यों को दर्शाता है। इनिशियलाइज़ेशन कॉपी कंस्ट्रक्टरperson b(a); द्वारा किया जाता है । इसका काम एक मौजूदा वस्तु की स्थिति के आधार पर एक नई वस्तु का निर्माण करना है। असाइनमेंट कॉपी असाइनमेंट ऑपरेटर द्वारा किया जाता है । इसका काम आम तौर पर थोड़ा अधिक जटिल है, क्योंकि लक्ष्य वस्तु पहले से ही कुछ वैध स्थिति में है, जिससे निपटने की आवश्यकता है।b = a

चूंकि हमने न तो खुद को कॉपी कंस्ट्रक्टर और न ही असाइनमेंट ऑपरेटर (न ही डिस्ट्रक्टर) के रूप में घोषित किया है, इसलिए ये हमारे लिए निहित हैं। मानक से उद्धरण:

[...] कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर, [...] और विध्वंसक विशेष सदस्य कार्य हैं। [ नोट : कार्यान्वयन कुछ वर्ग प्रकारों के लिए इन सदस्य कार्यों को स्पष्ट रूप से घोषित करेगा जब कार्यक्रम स्पष्ट रूप से उन्हें घोषित नहीं करता है। यदि उनका उपयोग किया जाता है तो कार्यान्वयन उन्हें स्पष्ट रूप से परिभाषित करेगा। [...] अंतिम नोट ] [n3126.pdf अनुभाग 12 ]1]

डिफ़ॉल्ट रूप से, किसी ऑब्जेक्ट को कॉपी करने का अर्थ है उसके सदस्यों की प्रतिलिपि बनाना:

गैर-संघ श्रेणी X के लिए अनुमानित रूप से परिभाषित कॉपी कंस्ट्रक्टर अपने सब -जेक्ट्स की एक सदस्यवार कॉपी करता है। [n3126.pdf सेक्शन 12.8 316]

गैर-संघ श्रेणी X के लिए अनुमानित रूप से परिभाषित कॉपी असाइनमेंट ऑपरेटर अपने सबोबिज के सदस्यवार कॉपी असाइनमेंट करता है। [n3126.pdf सेक्शन 12.8 330]

निहित परिभाषाएँ

इस personतरह से देखने के लिए स्पष्ट रूप से परिभाषित विशेष सदस्य कार्य :

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

इस मामले में हम जो चाहते हैं, ठीक उसी तरह से कॉपी करते हैं: nameऔर ageकॉपी किए जाते हैं, इसलिए हमें एक आत्म-निहित, स्वतंत्र personवस्तु मिलती है । अनुमानित रूप से परिभाषित विध्वंसक हमेशा खाली होता है। यह इस मामले में भी ठीक है क्योंकि हमने कंस्ट्रक्टर में कोई संसाधन हासिल नहीं किया था। personविध्वंसक समाप्त होने के बाद सदस्यों के विध्वंसक रूप से कहा जाता है:

विध्वंसक के शरीर को क्रियान्वित करने और शरीर के भीतर आवंटित किसी भी स्वचालित वस्तुओं को नष्ट करने के बाद, दसवीं कक्षा के लिए एक विध्वंसक एक्स के प्रत्यक्ष [...] सदस्यों के लिए विनाशकों को बुलाता है [n3126.pdf 12.4 ]6]

संसाधनों का प्रबंधन

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

हमें पूर्व-मानक C ++ के समय पर वापस जाना चाहिए। इस तरह की कोई बात नहीं थी std::string, और प्रोग्रामर पॉइंटर्स के साथ प्यार में थे। personवर्ग इस तरह दिख रही हो सकता है:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

आज भी, लोग अभी भी इस शैली में कक्षाएं लिखते हैं और मुसीबत में पड़ते हैं: " मैंने एक व्यक्ति को एक वेक्टर में धकेल दिया और अब मुझे पागल मेमोरी त्रुटियां मिलती हैं! " याद रखें कि डिफ़ॉल्ट रूप से, किसी वस्तु की नकल करने का अर्थ है उसके सदस्यों की नकल करना, लेकिन nameसदस्य की नकल करना एक पॉइंटर को कॉपी करता है, कि कैरेक्टर एरे की ओर इशारा करता है! इसके कई अप्रिय प्रभाव हैं:

  1. के माध्यम से परिवर्तन aदेखा जा सकता है b
  2. एक बार bनष्ट हो जाने के बाद , a.nameझूलने वाला सूचक है।
  3. यदि aनष्ट हो जाता है, तो झूलने वाले सूचक को हटाने से अपरिभाषित व्यवहार होता है
  4. चूंकि असाइनमेंट को ध्यान में नहीं रखा nameगया है, असाइनमेंट से पहले बताया गया है, जितनी जल्दी या बाद में आपको सभी जगह मेमोरी लीक मिल जाएगी।

स्पष्ट परिभाषाएँ

चूंकि सदस्यवार कॉपी का वांछित प्रभाव नहीं है, इसलिए हमें कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर को स्पष्ट रूप से वर्ण सरणी की गहरी प्रतियां बनाने के लिए परिभाषित करना चाहिए:

// 1. copy constructor
person(const person& that)
{
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    if (this != &that)
    {
        delete[] name;
        // This is a dangerous point in the flow of execution!
        // We have temporarily invalidated the class invariants,
        // and the next statement might throw an exception,
        // leaving the object in an invalid state :(
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }
    return *this;
}

आरंभीकरण और असाइनमेंट के बीच अंतर पर ध्यान दें: nameमेमोरी लीक को रोकने के लिए असाइन करने से पहले हमें पुरानी स्थिति को फाड़ देना चाहिए । साथ ही, हमें फॉर्म के सेल्फ असाइनमेंट से भी बचाव करना होगा x = x। उस जांच के बिना, स्रोत स्ट्रिंग delete[] nameवाले सरणी को हटा देगा , क्योंकि जब आप लिखते हैं , तो दोनों और एक ही सूचक होते हैं।x = xthis->namethat.name

अपवाद सुरक्षा

दुर्भाग्य से, यह समाधान विफल हो जाएगा यदि new char[...]स्मृति थकावट के कारण एक अपवाद फेंकता है। एक संभव समाधान एक स्थानीय चर शुरू करना और बयानों को फिर से व्यवस्थित करना है:

// 2. copy assignment operator
person& operator=(const person& that)
{
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
}

यह एक स्पष्ट जाँच के बिना स्व-असाइनमेंट का भी ध्यान रखता है। इस समस्या का एक और अधिक मजबूत समाधान प्रतिलिपि-और-स्व-मुहावरा है , लेकिन मैं यहां अपवाद सुरक्षा के विवरण में नहीं जाऊंगा। मैंने केवल निम्नलिखित बिंदु बनाने के लिए अपवादों का उल्लेख किया है: संसाधनों का प्रबंधन करने वाली कक्षाएं लिखना कठिन है।

गैर-संसाधन योग्य संसाधन

कुछ संसाधनों की प्रतिलिपि नहीं की जा सकती है या नहीं होनी चाहिए, जैसे फ़ाइल हैंडल या म्यूटेक्स। उस मामले में, बस privateएक परिभाषा दिए बिना कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर की घोषणा करें :

private:

    person(const person& that);
    person& operator=(const person& that);

वैकल्पिक रूप से, आप boost::noncopyableइन्हें हटा सकते हैं या इन्हें हटाए जाने की घोषणा कर सकते हैं (C ++ 11 और इसके बाद के संस्करण में):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

तीन का नियम

कभी-कभी आपको एक संसाधन को प्रबंधित करने वाले वर्ग को लागू करने की आवश्यकता होती है। (कभी भी एक ही वर्ग में कई संसाधनों का प्रबंधन न करें, इससे केवल पीड़ा होगी।) उस स्थिति में, तीन का नियम याद रखें :

यदि आपको स्पष्ट रूप से विध्वंसक, कॉपी कंस्ट्रक्टर या कॉपी असाइनमेंट ऑपरेटर की स्पष्ट रूप से घोषणा करने की आवश्यकता है, तो आपको संभवतः उन तीनों को स्पष्ट रूप से घोषित करने की आवश्यकता है।

(दुर्भाग्य से, यह "नियम" C ++ मानक या मेरे द्वारा ज्ञात किसी भी संकलक द्वारा लागू नहीं किया गया है।)

पाँच का नियम

C ++ 11 से, एक ऑब्जेक्ट में 2 अतिरिक्त विशेष सदस्य कार्य हैं: मूव कंस्ट्रक्टर और मूव असाइनमेंट। पांच राज्यों का नियम इन कार्यों को लागू करने के लिए भी है।

हस्ताक्षर के साथ एक उदाहरण:

class person
{
    std::string name;
    int age;

public:
    person(const std::string& name, int age);        // Ctor
    person(const person &) = default;                // Copy Ctor
    person(person &&) noexcept = default;            // Move Ctor
    person& operator=(const person &) = default;     // Copy Assignment
    person& operator=(person &&) noexcept = default; // Move Assignment
    ~person() noexcept = default;                    // Dtor
};

शून्य का नियम

3/5 के नियम को 0/3/5 के नियम के रूप में भी जाना जाता है। नियम का शून्य भाग बताता है कि आपको अपनी कक्षा बनाते समय विशेष सदस्य कार्यों में से किसी को भी लिखने की अनुमति नहीं है।

सलाह

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


4
फ्रेड, मैं अपने अप-वोट के बारे में बेहतर महसूस करूंगा यदि (ए) आप प्रतिलिपि किए गए कोड में बुरी तरह से कार्यान्वित किए गए वर्तनी को नहीं समझेंगे और यह कहते हुए एक नोट जोड़ देंगे कि यह गलत है और फ़ाइनप्रिंट में कहीं और देखें; या तो कोड में c & s का उपयोग करें या सिर्फ इन सभी सदस्यों (B) को लागू करने पर छोड़ दें, जिन्हें आप पहले आधे हिस्से को छोटा कर देंगे, जिसका RoT से कोई लेना-देना नहीं है; (ग) आप चाल शब्दार्थों की शुरूआत और RoT के लिए इसका क्या अर्थ है, इस पर चर्चा करेंगे।
भारतीय स्टेट बैंक

7
लेकिन फिर पोस्ट को सी / डब्ल्यू बनाया जाना चाहिए, मुझे लगता है। मुझे यह पसंद है कि आप शर्तों को अधिकांशतः सटीक रखते हैं (यानी कि आप " कॉपी असाइनमेंट ऑपरेटर" कहते हैं, और यह कि आप उस सामान्य जाल में नहीं फंसते हैं जो असाइनमेंट कॉपी नहीं कर सकता है)।
जोहान्स शहाब -

4
@Prasoon: मुझे नहीं लगता कि उत्तर का आधा हिस्सा गैर-सीडब्ल्यू उत्तर के "निष्पक्ष संपादन" के रूप में देखा जाएगा।
sbi

69
यह बहुत अच्छा होगा यदि आप C ++ 11 के लिए अपनी पोस्ट को अपडेट करते हैं (यानी कंस्ट्रक्टर / असाइनमेंट को स्थानांतरित करें)
अलेक्जेंडर मालाखोव

5
@solalito आपको उपयोग के बाद कुछ भी जारी करना होगा: संगामिति ताले, फ़ाइल हैंडल, डेटाबेस कनेक्शन, नेटवर्क सॉकेट, हीप मेमोरी ...
fredoverflow

509

तीन के नियम सी ++ के लिए अंगूठे का एक नियम है, मूल रूप से कह रही है

यदि आपकी कक्षा को किसी की आवश्यकता है

  • एक प्रतिलिपि निर्माता ,
  • एक असाइनमेंट ऑपरेटर ,
  • या एक विध्वंसक ,

स्पष्ट रूप से परिभाषित किया गया है, तो इसके तीनों की आवश्यकता है

इसका कारण यह है कि उनमें से तीनों का उपयोग आमतौर पर एक संसाधन का प्रबंधन करने के लिए किया जाता है, और यदि आपकी कक्षा एक संसाधन का प्रबंधन करती है, तो इसे आमतौर पर नकल के साथ-साथ मुक्त प्रबंधन की आवश्यकता होती है।

यदि आपके वर्ग के संसाधन को कॉपी करने के लिए कोई अच्छा शब्दार्थ नहीं है, तो कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर के रूप में घोषित ( परिभाषित नहीं ) करके कॉपी करने से मना करें private

(ध्यान दें कि C ++ मानक का आगामी नया संस्करण (जो कि C ++ 11 है), C अर्थ को स्थानांतरित करता है C ++, जो संभवतः तीन के नियम को बदल देगा। थ्री के नियम के बारे में।)


3
नकल को रोकने के लिए एक और उपाय एक वर्ग से विरासत (निजी तौर पर) है जिसे कॉपी (जैसे boost::noncopyable) नहीं किया जा सकता है । यह ज्यादा साफ भी हो सकता है। मुझे लगता है कि C ++ 0x और "डिलीट" कार्यों की संभावना यहां मदद कर सकती है, लेकिन वाक्यविन्यास भूल गए: /
Matthieu M.

2
@ मैथ्यू: हां, यह भी काम करता है। लेकिन जब तक noncopyableएसटीडी लिब का हिस्सा नहीं होता, मैं इसे ज्यादा सुधार नहीं मानता। (ओह, और अगर आप डिलीट सिंटैक्स भूल गए, तो आप मोर एथन को भूल गए जो मैं कभी भी जानता था। :))
sbi

3
@ दान: इस जवाब को देखें । हालांकि, मैं ज़ीरो के मार्टिनो के नियम से चिपके रहने की सलाह दूंगा । मेरे लिए, यह पिछले दशक में गढ़े गए C ++ के लिए सबसे महत्वपूर्ण नियमों में से एक है।
sbi

3
मार्खो का नियम अब शून्य से बेहतर (स्पष्ट एडवेयर टेकओवर के बिना) आर्काइव.ऑर्ग
नाथन किड

161

बड़े तीन का कानून ऊपर निर्दिष्ट है।

एक आसान उदाहरण, सादे अंग्रेजी में, इस तरह की समस्या हल करती है:

गैर डिफ़ॉल्ट विध्वंसक

आपने अपने निर्माता में मेमोरी आवंटित की है और इसलिए इसे हटाने के लिए आपको एक विध्वंसक लिखना होगा। अन्यथा आप स्मृति रिसाव का कारण बनेंगे।

आप सोच सकते हैं कि यह काम किया गया है।

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

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

इसलिए, आप एक कॉपी कंस्ट्रक्टर लिखते हैं ताकि यह नई वस्तुओं को नष्ट करने के लिए स्मृति के अपने टुकड़ों को आवंटित करे।

असाइनमेंट ऑपरेटर और कॉपी कंस्ट्रक्टर

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

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

इसे हल करने के लिए आप कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर का अपना संस्करण लिखें। आपके संस्करण नई वस्तुओं को अलग-अलग मेमोरी आवंटित करते हैं और उन मानों की प्रतिलिपि बनाते हैं जो पहला सूचक इसके पते के बजाय इंगित कर रहा है।


4
इसलिए यदि हम एक कॉपी कंस्ट्रक्टर का उपयोग करते हैं तो कॉपी बनाई जाती है लेकिन पूरी तरह से एक अलग मेमोरी लोकेशन पर और यदि हम कॉपी कंस्ट्रक्टर का उपयोग नहीं करते हैं तो कॉपी बनाई जाती है लेकिन यह उसी मेमोरी लोकेशन की ओर इशारा करता है। क्या आप कहना चाह रहे हैं? तो कॉपी कंस्ट्रक्टर के बिना एक कॉपी का मतलब है कि एक नया पॉइंटर होगा लेकिन उसी मेमोरी लोकेशन की ओर इशारा करता है, लेकिन अगर हम कॉपी कंस्ट्रक्टर को उपयोगकर्ता द्वारा स्पष्ट रूप से परिभाषित करते हैं, तो हमारे पास एक अलग पॉइंटर पॉइंट होगा जो एक अलग मेमोरी लोकेशन पर होगा लेकिन डेटा होगा।
अटूट

4
क्षमा करें, मैंने इस युग का उत्तर दिया था, लेकिन मेरा उत्तर अभी भी यहाँ नहीं लगता है :-( मूल रूप से, हाँ - आप इसे प्राप्त करें :-)
स्टीफन

1
सिद्धांत प्रति असाइनमेंट ऑपरेटर को कैसे लागू करता है? यह उत्तर अधिक उपयोगी होगा यदि तीन के नियम में 3 का उल्लेख किया जाएगा।
DBedrenko

1
@DBedrenko, "आप एक कॉपी कंस्ट्रक्टर लिखते हैं ताकि यह नई वस्तुओं को स्मृति के अपने टुकड़ों को आवंटित करे ..." यह वही सिद्धांत है जो कॉपी असाइनमेंट ऑपरेटर तक फैलता है। क्या आपको नहीं लगता कि मैंने यह स्पष्ट किया है?
स्टीफन

2
@DBedrenko, मैंने कुछ और जानकारी जोड़ी है। क्या यह स्पष्ट है?
स्टीफन

44

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

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

यदि MyClass में केवल कुछ आदिम टाइप किए गए सदस्य हैं, तो एक डिफ़ॉल्ट असाइनमेंट ऑपरेटर काम करेगा, लेकिन अगर इसमें कुछ पॉइंटर सदस्य और ऑब्जेक्ट हैं, जिनके पास असाइनमेंट ऑपरेटर नहीं हैं, तो परिणाम अप्रत्याशित होगा। इसलिए हम कह सकते हैं कि यदि किसी वर्ग के विध्वंसक को हटाने के लिए कुछ है, तो हमें एक गहरी कॉपी ऑपरेटर की आवश्यकता हो सकती है, जिसका अर्थ है कि हमें एक कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर प्रदान करना चाहिए।


36

किसी वस्तु की नकल करने का क्या मतलब है? कुछ तरीके हैं जिनसे आप वस्तुओं को कॉपी कर सकते हैं - चलो उन 2 प्रकारों के बारे में बात करते हैं जिनकी आप सबसे अधिक संभावना रखते हैं - गहरी प्रतिलिपि और उथली प्रतिलिपि।

चूंकि हम एक ऑब्जेक्ट-ओरिएंटेड भाषा में हैं (या कम से कम ऐसा मान रहे हैं), मान लें कि आपके पास स्मृति का एक टुकड़ा आवंटित है। चूंकि यह एक ओओ-भाषा है, इसलिए हम आसानी से उन मेमोरीज़ को देख सकते हैं जो हम आवंटित करते हैं क्योंकि वे आमतौर पर आदिम वैरिएबल (इन्ट्स, चार्ट्स, बाइट्स) या वे कक्षाएं होती हैं जिन्हें हम परिभाषित करते हैं जो हमारे अपने प्रकार और प्राइमिटिव से बने होते हैं। तो चलिए बताते हैं कि हमारे पास निम्नानुसार कार की एक क्लास है:

class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;

public changePaint(String newColor)
{
   this.sPrintColor = newColor;
}

public Car(String model, String make, String color) //Constructor
{
   this.sPrintColor = color;
   this.sModel = model;
   this.sMake = make;
}

public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}

public Car(const Car &other) // Copy Constructor
{
   this.sPrintColor = other.sPrintColor;
   this.sModel = other.sModel;
   this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
   if(this != &other)
   {
      this.sPrintColor = other.sPrintColor;
      this.sModel = other.sModel;
      this.sMake = other.sMake;
   }
   return *this;
}

}

एक गहरी प्रति है अगर हम एक वस्तु की घोषणा करते हैं और फिर वस्तु की एक पूरी तरह से अलग प्रतिलिपि बनाते हैं ... हम 2 वस्तुओं के साथ 2 पूरी तरह से मेमोरी सेट करते हैं।

Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.

अब कुछ अजीब करते हैं। मान लीजिए कि कार 2 या तो गलत तरीके से प्रोग्राम किया गया है या जानबूझकर वास्तविक मेमोरी को साझा करने के लिए है जो कार 1 से बना है। (यह आम तौर पर ऐसा करने के लिए एक गलती है और कक्षाओं में आमतौर पर इसके तहत चर्चा की गई कंबल है।) पहले से ही पूछें कि आप कभी भी कार 2 के बारे में पूछते हैं, आप वास्तव में कार 1 की मेमोरी स्पेस के लिए एक पॉइंटर को हल कर रहे हैं ... यह कम या ज्यादा एक उथली प्रतिलिपि है। है।

//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

 Car car1 = new Car("ford", "mustang", "red"); 
 Car car2 = car1; 
 car2.changePaint("green");//car1 is also now green 
 delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve 
 the address of where car2 exists and delete the memory...which is also
 the memory associated with your car.*/
 car1.changePaint("red");/*program will likely crash because this area is
 no longer allocated to the program.*/

इसलिए आप जिस भाषा में लिख रहे हैं, उसकी परवाह किए बिना, जब आप वस्तुओं की नकल करने की बात करते हैं, तो इस बात से बहुत सावधान रहें कि आप ज्यादातर समय एक गहरी प्रतिलिपि चाहते हैं।

कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर क्या हैं? मैंने पहले ही उन्हें ऊपर इस्तेमाल किया है। कॉपी कंस्ट्रक्टर को तब बुलाया जाता है जब आप कोड टाइप करते हैं जैसे कि Car car2 = car1; अनिवार्य रूप से यदि आप एक वैरिएबल घोषित करते हैं और इसे एक लाइन में असाइन करते हैं, तो जब कॉपी कंस्ट्रक्टर को कॉल किया जाता है। असाइनमेंट ऑपरेटर वही होता है जब आप एक समान साइन का उपयोग करते हैं - car2 = car1;। नोटिस car2एक ही बयान में घोषित नहीं किया गया है। इन कार्यों के लिए आपके द्वारा लिखे गए कोड के दो भाग बहुत समान हैं। वास्तव में विशिष्ट डिज़ाइन पैटर्न में एक और फ़ंक्शन होता है जिसे आप एक बार सब कुछ सेट करने के लिए कहते हैं, जब आप संतुष्ट हो जाते हैं कि प्रारंभिक कॉपी / असाइनमेंट वैध है - यदि आप मेरे द्वारा लिखे गए लॉन्गैंड कोड को देखते हैं, तो फ़ंक्शन लगभग समान हैं।

मुझे उन्हें खुद कब घोषित करने की आवश्यकता है? यदि आप ऐसा कोड नहीं लिख रहे हैं जिसे किसी तरीके से साझा किया जाना है या उत्पादन के लिए है, तो आपको वास्तव में केवल उनकी आवश्यकता होने पर उन्हें घोषित करने की आवश्यकता है। आपको यह जानने की आवश्यकता है कि यदि आप इसे 'दुर्घटना से' इस्तेमाल करने के लिए चुनते हैं तो आपकी प्रोग्राम भाषा क्या करती है और आपने इसे नहीं बनाया है - यानी आपको कंपाइलर डिफ़ॉल्ट मिलता है। मैं शायद ही कभी उदाहरण के लिए कॉपी कंस्ट्रक्टर का उपयोग करता हूं, लेकिन असाइनमेंट ऑपरेटर ओवरराइड बहुत आम हैं। क्या आप जानते हैं कि आप इसके अलावा, घटाव, इत्यादि को भी ओवरराइड कर सकते हैं?

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


5
प्रश्न को C ++ टैग किया गया था। यह छद्म कोड प्रदर्शनी अच्छी तरह से परिभाषित "तीन के नियम" के बारे में कुछ भी स्पष्ट करने के लिए बहुत कम करता है, और बस सबसे खराब रूप से भ्रम फैलाता है।
सेह

26

मुझे उन्हें खुद कब घोषित करने की आवश्यकता है?

तीन नियमों में कहा गया है कि यदि आप किसी भी की घोषणा करते हैं

  1. कॉपी कंस्ट्रक्टर
  2. कॉपी असाइनमेंट ऑपरेटर
  3. नाशक

तो आपको तीनों की घोषणा करनी चाहिए। यह अवलोकन से बाहर हुआ कि कॉपी ऑपरेशन के अर्थ को लेने की आवश्यकता लगभग हमेशा किसी न किसी तरह के संसाधन प्रबंधन को करने वाले वर्ग से होती है, और यह लगभग हमेशा निहित होता है

  • एक कॉपी ऑपरेशन में जो भी संसाधन प्रबंधन किया जा रहा था, वह शायद दूसरे कॉपी ऑपरेशन और में किया जाना चाहिए

  • वर्ग विध्वंसक भी संसाधन के प्रबंधन (आमतौर पर इसे जारी करने) में भाग ले रहा होगा। प्रबंधित किया जाने वाला क्लासिक संसाधन मेमोरी था, और यही कारण है कि सभी मानक लाइब्रेरी कक्षाएं जो मेमोरी का प्रबंधन करती हैं (जैसे, एसटीएल कंटेनर जो गतिशील मेमोरी प्रबंधन करते हैं) सभी "बड़े तीन" की घोषणा करते हैं: दोनों कॉपी ऑपरेशन और एक विध्वंसक।

तीन के नियम का एक परिणाम यह है कि एक उपयोगकर्ता-घोषित विध्वंसक की उपस्थिति इंगित करती है कि कक्षा में प्रतिलिपि संचालन के लिए सरल सदस्य वार प्रति उपयुक्त नहीं है। बदले में, यह सुझाव देता है कि यदि कोई वर्ग एक विध्वंसक घोषित करता है, तो कॉपी संचालन शायद स्वचालित रूप से उत्पन्न नहीं होना चाहिए, क्योंकि वे सही काम नहीं करेंगे। जिस समय C ++ 98 को अपनाया गया था, उस तर्क की इस पंक्ति के महत्व को पूरी तरह से सराहा नहीं गया था, इसलिए C ++ 98 में, एक उपयोगकर्ता द्वारा घोषित विध्वंसक के अस्तित्व को कॉपी ऑपरेशन उत्पन्न करने के लिए संकलक की इच्छा पर कोई प्रभाव नहीं पड़ा। C ++ 11 में यह जारी है, लेकिन केवल इसलिए कि जिन शर्तों के तहत प्रतिलिपि कार्रवाई उत्पन्न होती हैं उन्हें प्रतिबंधित करने से बहुत अधिक विरासत कोड टूट जाएगा।

मैं अपनी वस्तुओं को कॉपी होने से कैसे रोक सकता हूं?

निजी एक्सेस स्पेसियर के रूप में कॉपी कंस्ट्रक्टर और कॉपी असाइनमेंट ऑपरेटर की घोषणा करें।

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

C ++ 11 में आप कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर को हटा सकते हैं

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

16

मौजूदा उत्तरों में से कई पहले से ही कॉपी कंस्ट्रक्टर, असाइनमेंट ऑपरेटर और डिस्ट्रक्टर को छूते हैं। हालाँकि, C ++ 11 में, पोस्ट सिमेंटिक की शुरूआत 3 से परे इसका विस्तार कर सकती है।

हाल ही में माइकल क्लैसे ने एक बात की जो इस विषय को छूती है: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class


10

C ++ में तीन का नियम डिजाइन का एक मूलभूत सिद्धांत और तीन आवश्यकताओं का विकास है यदि निम्न सदस्य फ़ंक्शन में से एक में स्पष्ट परिभाषा है, तो प्रोग्रामर को अन्य दो सदस्यों के कार्यों को एक साथ परिभाषित करना चाहिए। निम्नलिखित तीन सदस्य कार्य अपरिहार्य हैं: विध्वंसक, कॉपी कंस्ट्रक्टर, कॉपी असाइनमेंट ऑपरेटर।

C ++ में कॉपी कंस्ट्रक्टर एक विशेष कंस्ट्रक्टर है। इसका उपयोग एक नई वस्तु के निर्माण के लिए किया जाता है, जो कि किसी मौजूदा वस्तु की नकल के बराबर नई वस्तु है।

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

इसके त्वरित उदाहरण हैं:

// default constructor
My_Class a;

// copy constructor
My_Class b(a);

// copy constructor
My_Class c = a;

// copy assignment operator
b = a;

7
नमस्ते, आपका जवाब कुछ नया नहीं जोड़ता है। अन्य लोग विषय को बहुत अधिक गहराई में रखते हैं, और अधिक सटीक रूप से - आपका उत्तर अनुमानित है और वास्तव में कुछ स्थानों पर गलत है (अर्थात यहां "कोई" नहीं होना चाहिए; यह "बहुत संभवतः" होना चाहिए)। यह वास्तव में आपके लायक नहीं होगा इस तरह के प्रश्नों के उत्तर पोस्ट करते हुए जो पहले से ही पूरी तरह से उत्तर दे चुके हैं। जब तक आपके पास जोड़ने के लिए नई चीजें नहीं हैं।
Mat

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