Indicatoare inteligente

Lucrul cu indicii poate duce uneori la erori dacă nu le folosiți foarte atent. De exemplu, pot avea dificultăți dacă uit să le inițializez, să uit să le șterg la sfârșitul lucrării și să șterg de două ori același indicator.

Principala problemă cu utilizarea pointerilor este că atunci când lucrați cu memorie dinamică, trebuie să dezvoltați un sistem care să vă gestioneze memoria proprie. Ea va trebui să știe unde să aloce memoria și unde să elibereze, ceea ce nu este întotdeauna o sarcină banală. Uneori aplicațiile dezvoltate sunt atât de complexe încât nu este posibilă eliberarea memoriei ocupate exact unde a fost alocată. Și este adesea dificil să se determine durata de viață a resurselor utilizate.

Clasele din C ++ sunt "inteligente". Spre deosebire de limbajul C, pe lângă metode, au constructori și distrugători. Astfel, apare noțiunea de "viață" - timpul de la chemarea constructorului la chemarea distrugătorului. Deși, de fapt, obiectul este considerat viu după ce constructorul este executat complet înainte de începerea apelului destructor. Nu putem spune că obiectul este în viață în momentul executării lor, după cum nu este garantat faptul că toate câmpurile sunt inițializate și non-triviale pentru a lucra cu ei poate fi plină de probleme.

void foo () A a;
// muncă utilă
// sunați-l pe distrugător
>

void foo () A a;
// muncă utilă
// sunați-l pe distrugător
>
// muncă utilă
>

bar (A ());
// va fi distrugătorul
// chemat după
// sfârșit
// executând bara ()

Cu selecția dinamică, obiectul va exista până când îl vom elimina. Când programul termină executarea ei, nimeni nu va apela destructorul obiectelor care nu sunt un programator nu se elimina, astfel încât dacă există orice cod de important (cum ar fi închiderea conexiunii), acesta nu va fi executat. În acest caz, durata de viață a obiectului este limitată la ștergerea indicatorului folosind ștergerea. Dacă obiectul nu este șters înainte de a ieși din zona de vizibilitate, atunci se transformă într-un "zombie" - există, dar nu îl puteți accesa.

void foo () A a = nou A ();
// obiectul nu va fi distrus până când nu este chemat ștergerea
>

Un "pointer inteligent" asociază durata de viață a unui obiect într-o memorie dinamică cu durata de viață a unui obiect alocat pe stivă.

Să luăm în considerare, în comparație cu codul de lucru cu pointerul obișnuit și dezvoltat inteligent. Vom construi pe clasa deja dezvoltată BigInt.

BigInt * p = BigInt nou ();
(* p) = n;
//.
șir str = p-> toString ();
//.
șterge p;

Problema cu utilizarea unui pointer obișnuit este că undeva în text se poate apela o întoarcere. iar în acest caz programul nu ajunge la locul de îndepărtare. Cu toate acestea, ștergerea trebuie să fie întotdeauna apelată când este efectuată alocarea memoriei cu noi. Puteți să scăpați de necesitatea de ao numi manual, folosind un indicator inteligent, sintaxa de comunicare cu care este similar cu sintaxa de a comunica cu un pointer obișnuit. În acest caz, curățarea memoriei poate fi mutată la destructorul indicatorului inteligent.

Să descriem clasa indicatorului inteligent dezvoltat.

privat:
BigInt * ptr_;

publice:
BigIntPtr (BigInt * p = 0);

BigIntPtr ();
BIGINT operator * () const;
Operatorul BigInt * -> () const;

privat:
BigIntPtr (BigIntPtr const );
BigIntPtr operator = (BigIntPtr const );
>;

BigIntPtr :: BigIntPtr (BigInt * p): ptr_ (p)>

BigIntPtr () șterge ptr_;
>

BIGINT BigIntPtr :: operatorul * () const return * ptr_;
>

BigInt * BigIntPtr :: operatorul -> () const return retur;
>

Trebuie de asemenea remarcat faptul că, conform regulii mnemonice, atunci când definiți distrugătorul, trebuie să definiți și constructorul de copie și operatorul de atribuire. Acest lucru se datorează faptului că, dacă vrem să distrugem obiectul este non-trivială (de exemplu, pentru a elibera memoria alocată anterior), chiar și atunci când copierea dintr-un constructor sau de atribuire poate cauza probleme.

Operatorul unar "asterisc" este o constantă, dar returnează o referință non-const. Deoarece indicatorul inteligent care se dezvoltă trebuie să se comporte ca un pointer obișnuit, trebuie să permitem ca obiectul să fie schimbat pentru a fi referit. Cu toate acestea, înseamnă că indicatorul inteligent în sine nu se schimbă în acest caz.

Nu este nevoie să rezolvați excepția dacă indicatorul inteligent nu se referă la nimic (adică ptr_ este zero). Faptul că indicatorul normal și nu reacționează la un astfel de comportament, și nu este clar ce ar trebui să fie returnate în acest caz, este inteligent.

Operatorul "săgeată" trebuie să returneze un indicator. Deși, de fapt, poate returna orice, care are un operator "săgeată", dar apoi apelul către acest operator va fi repetat până când pointerul va fi în cele din urmă returnat.

Distrugătorul indicatorului inteligent ne salvează de a nu fi nevoit să ștergem memoria, deoarece face acest lucru în sine. Astfel, obiectul la care se referă indicatorul inteligent va fi distrus imediat după ieșirea din blocul de vizibilitate a indicatorului inteligent.

Acest concept se numește RAII - Inițierea resurselor este inițializarea (alocarea resurselor este inițializarea). Aceasta înseamnă că plasăm inițializarea pe obiectul însuși când îi alocăm memorie și, de asemenea, îl facem responsabil pentru returnarea memoriei când este distrusă. Exemplele includ conexiuni baze de date, porturi solicitate, mutexuri în aplicații paralele. Dacă obiectul nu returnează resursa solicitată în timpul inițializării, atunci nimeni după ce o poate folosi.

Codul dezvoltat mai sus are multe deficiențe. În primul rând, un asemenea indicator nu poate fi schimbat. Acest lucru poate fi corectat prin definirea unui operator de atribuire a cărui parte dreaptă este un pointer la obiectul dorit.

Astfel, nu pot exista două indicii la un obiect. Distructorul pentru primul indicator inteligent va distruge obiectul, iar cel de-al doilea distrugător de pointer va încerca să facă același lucru. În acest caz, va exista o eroare de ștergere dublă.

În plus, nu este posibil să se verifice dacă un astfel de indicator inteligent se referă la zero.

De asemenea, astfel de pointeri nu pot fi comparate cu pointerii convenționali. Dacă le transmiteți la funcția de comparare, atunci dacă constructorul care acceptă un pointer obișnuit nu este declarat explicit. atunci va fi creat un pointer inteligent, iar după ieșirea acestuia, destructorul obiectului temporar va șterge obiectul în sine.

Conceptul unui astfel de pointer este de obicei numit Pointer Scoped - pointerul scopului. Un astfel de indicator inteligent poate fi folosit numai în blocul pe care este declarat.

O altă implementare este Pointerul partajat, un pointer comun. Diferența este că un astfel de pointer poate fi copiat. În acest caz, obiectul însuși va fi șters numai când ultimul indicator care îl face este distrus. Acest lucru devine posibil atunci când se utilizează numărul de referință.

Ideea este aceasta: atunci când este creat un pointer inteligent, acesta la rândul său, creează un număr de referință, un număr care este stocat pe heap, specificând numărul de indicii inteligente care se referă la un obiect. Când copiați un pointer, acesta copiază nu numai pointerul la obiect, ci și un indicator la același contor, iar valoarea acestuia crește. Când distrugi un indicator inteligent, numărul scade. Dacă pointerul distrus a fost ultimul proprietar al acestui obiect, atunci și obiectul este șters.

Indicatoare inteligente

O altă variantă a pointerului comun este crearea unui obiect suplimentar în memoria dinamică, care conține atât contorul cât și o referință la obiectul stocat. Astfel, atunci când copiați un singur pointer vor fi copiate, fiecare indicator inteligent se va referi doar la un obiect cu un contor și o legătură cu originalul.

Indicatoare inteligente

Astfel de semne pot fi deja atribuite reciproc, transmite o copie la funcția de a compara și mai mult.

Articole similare