C++վերաբերվում է օգտագործողի կողմից սահմանված տիպերի փոփոխականներին արժեքային իմաստաբանությամբ: Սա նշանակում է, որ օբյեկտները անուղղակիորեն պատճենվում են տարբեր համատեքստերում:
Եկեք դիտարկենք մի պարզ օրինակ.
class Person { std::string name; int age; public: // member initializer list : name(name), age(age) Person(const std::sting& name, int age) : name(name), age(age) { } }; int main() { Person p("Sreenath Mopuri", 30); Person q(p) // what happens here? q = p; // And here? }
Անդամների հատուկ գործառույթներ
Ի՞նչ է նշանակում պատճենել անձի օբյեկտը: Հիմնական գործառույթը ցույց է տալիս պատճենման երկու հստակ սցենար: Նախաստորագրումը Person q(p); կատարվում է պատճենի կառուցողի կողմից: նրա խնդիրն է՝ կառուցել թարմ օբյեկտ՝ հիմնվելով գոյություն ունեցող օբյեկտի վիճակի վրա: Հանձնարարությունըq = pկատարվում է պատճենահանման օպերատորի կողմից: Դրա գործը, ընդհանուր առմամբ, մի փոքր ավելի բարդ է, քանի որ թիրախային օբյեկտն արդեն որոշակի վավեր վիճակում է, որը պետք է լուծվի:
Քանի որ մենք ինքներս չենք հայտարարել ոչ պատճենի կառուցողին, ոչ էլ հանձնարարականի օպերատորին (ոչ դեստրուկտորին), դրանք մեզ համար անուղղակիորեն սահմանված են:
Իրականացումը անուղղակիորեն կհայտարարի անդամի այս գործառույթները դասերի տեսակների համար, երբ ծրագիրը հստակորեն չի հայտարարում դրանք:
Անհայտ սահմանումներ
Անդամների անուղղակիորեն սահմանված գործառույթները Person-ի համար այսպիսի տեսք ունեն.
// 1. copy constructor Person(const Person& p) : name(p.name), age(p.gae) { } // 2. copy assignment operator Person& operator=(const Person& p) { name = p.name; age = p.age; return *this; } // 3. destructor ~Person() { }
Անդամների իմաստուն պատճենումը հենց այն է, ինչ մենք ուզում ենք այս դեպքում. անունըև տարիքըպատճենվում են, այնպես որ մենք ստանում ենք ինքնամփոփ, անկախ Անձնություն ուժեղ> օբյեկտ. Անուղղակիորեն սահմանված դեստրուկտորը միշտ դատարկ է: Սա նույնպես լավ է այս դեպքում, քանի որ մենք ոչ մի ռեսուրս չենք ձեռք բերել կոնստրուկտորում։ Անդամի դեստրուկտորները անուղղակիորեն կանչվում են Անձի կործանիչի ավարտից հետո:
Պաշարների կառավարում
Ուրեմն ե՞րբ պետք է հստակորեն հայտարարենք հատուկ անդամի գործառույթները: Երբ մեր դասարանը կառավարում է ռեսուրսը, այսինքն, երբ դասի օբյեկտը պատասխանատու է այդ ռեսուրսի համար: Դա սովորաբար նշանակում է, որ ռեսուրսը ձեռք է բերվում կոնստրուկտորում (կամ փոխանցվում է կոնստրուկտորին) և ազատվում է կործանիչում:
Եկեք հետ գնանք ժամանակի նախ ստանդարտ 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* name_, int age_) { name = new char[strlen(name_) + 1]; // +1 for null terminate char strcpy(name, name_); age = age_; } // the destructor must release this resource via delete[] ~Person() { delete[] name; } };
Նույնիսկ այսօր ծրագրավորողները դեռ գրում են այս ոճով և դժվարությունների մեջ են ընկնում. «եթե մենք «Person» օբյեկտը մղում ենք վեկտորի մեջ, և այժմ մենք ստանում ենք խելահեղ հիշողության սխալներ»: Հիշեք, որ լռելյայնորեն պատճենելը: Օբյեկտը նշանակում է պատճենել իր անդամներին, բայց անդամի անունը պատճենելը պարզապես պատճենում է ցուցիչը, այլ ոչ թե այն նիշերի զանգվածը, որին նա մատնանշում է: Սա մի քանի տհաճ հետևանքներ ունի.
- Փոփոխությունները p-ի միջոցով կարելի է դիտարկել q:-ի միջոցով:
- Երբ qոչնչացվի,p.name-ը կախված ցուցիչ է:
- Եթե p-ը ոչնչացվի, կախվող ցուցիչը ջնջելով՝ չսահմանված վարքագիծ կստացվի:
- Քանի որ առաջադրանքը հաշվի չի առնում, թե ինչ անվան վրա է նշված հանձնարարությունը կատարելուց առաջ, վաղ թե ուշ դուք կունենաք հիշողության արտահոսքամբողջ տեղում:
Բացահայտ սահմանումներ
Քանի որ անդամի իմաստուն պատճենումը չի տալիս ցանկալի էֆեկտը, մենք պետք է հստակորեն սահմանենք պատճենի կոնստրուկտորը և պատճենի նշանակման օպերատորը՝ նիշերի զանգվածի խորը պատճենները ստեղծելու համար՝
// 1. copy constructor Person(const Person& p) { name = new char[strlen(p.name) + 1]; strcpy(name, p.name); age = p.age; } // 2. copy assignment operator Person operator = (const Person& p) { if (this != &p) { delete[] name; name = new char[strlen(p.name) + 1]; strcpy(name, p.name); age = p.age; } return *this; }
Նշում. տարբերությունը սկզբնավորման և նշանակման միջև, մենք պետք է քանդենք հին վիճակը՝ նախքան այն անվանել, որպեսզի կանխենք հիշողության արտահոսքը:
Խորհուրդ
Շատ ժամանակ, դուք կարիք չունեք ինքներդ կառավարել ռեսուրսը, քանի որ գոյություն ունեցող դասը, ինչպիսին է std::string-ը, արդեն դա անում է ձեզ համար: Պարզապես համեմատեք պարզ կոդը՝ օգտագործելով std::string անդամը խճճված և սխալների հակված այլընտրանքի հետ՝ օգտագործելով char*և դուք պետք է համոզվեք: Քանի դեռ դուք հեռու եք մնում չմշակված ցուցիչի անդամներից:
Շնորհակալություն այս հոդվածը կարդալու համար: Հարցերի դեպքում թողեք մեկնաբանություն ստորև: