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» օբյեկտը մղում ենք վեկտորի մեջ, և այժմ մենք ստանում ենք խելահեղ հիշողության սխալներ»: Հիշեք, որ լռելյայնորեն պատճենելը: Օբյեկտը նշանակում է պատճենել իր անդամներին, բայց անդամի անունը պատճենելը պարզապես պատճենում է ցուցիչը, այլ ոչ թե այն նիշերի զանգվածը, որին նա մատնանշում է: Սա մի քանի տհաճ հետևանքներ ունի.

  1. Փոփոխությունները p-ի միջոցով կարելի է դիտարկել q:-ի միջոցով:
  2. Երբ qոչնչացվի,p.name-ը կախված ցուցիչ է:
  3. Եթե p-ը ոչնչացվի, կախվող ցուցիչը ջնջելով՝ չսահմանված վարքագիծ կստացվի:
  4. Քանի որ առաջադրանքը հաշվի չի առնում, թե ինչ անվան վրա է նշված հանձնարարությունը կատարելուց առաջ, վաղ թե ուշ դուք կունենաք հիշողության արտահոսքամբողջ տեղում:

Բացահայտ սահմանումներ

Քանի որ անդամի իմաստուն պատճենումը չի տալիս ցանկալի էֆեկտը, մենք պետք է հստակորեն սահմանենք պատճենի կոնստրուկտորը և պատճենի նշանակման օպերատորը՝ նիշերի զանգվածի խորը պատճենները ստեղծելու համար՝

// 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*և դուք պետք է համոզվեք: Քանի դեռ դուք հեռու եք մնում չմշակված ցուցիչի անդամներից:

Շնորհակալություն այս հոդվածը կարդալու համար: Հարցերի դեպքում թողեք մեկնաբանություն ստորև: