momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

"Hello" şi "Goodbye" - constructor, destructor şi this în C++

C++
2013 oct

Cu struct ne-am familiarizat deja ("conform programei şcolare") în clasa a X-a, la "Prelucrarea datelor de tip structură/record/înregistrare"; dar - conform programei (în pofida vizării de "programare în C++") - această familiarizare s-a limitat la nivelul oferit de limbajul C, unde "struct" ambalează într-un tot unitar (numit "record", sau "înregistrare") date de diverse tipuri (numai date, nu şi funcţii).

Să se scrie un program C++ care să afişeze "Hello!" după lansarea în execuţie şi "Goodbye!" la încheiere, dar al cărui funcţie main() să fie vidă (dintr-o baterie de teste pentru angajare).

La încărcarea în memorie a programului executabil, sistemul de operare va configura memoria de alocat acelui program conform indicaţiilor privitoare la "zona de date" şi "zona de stivă", găsite în antetul acelui executabil; lansarea în execuţie implică imediat, constituirea în memorie a variabilelor globale existente în program.

Încheierea execuţiei atrage imediat şi eliberarea de către sistemul de operare, a resurselor alocate acelui program; aceasta implică şi invocarea automată a unui "destructor" - fie cel implicit (pus la dispoziţie pentru orice program), fie unul existent ca atare în program. Un destructor explicitat în program poate prevedea eventual şi alte acţiuni, pe lângă aceea de a cere sistemului să elibereze anumite zone din memoria alocată programului.

Soluţia exerciţiului enunţat mai sus constă în a instanţia un obiect "învăţat" să afişeze anumite mesaje în momentul creării şi respectiv, în momentul invocării finale a destructorului:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

struct toSay {
    // Constructor de exemplare ("instanţe") ale acestei structuri
    toSay() { 
        std::cout << "Hello!" << "\tI am at address: " << this << '\n'; 
    }
    // Destructor 
    ~toSay() { 
        std::cout << "Goodbye!\n"; 
    }
};

toSay test, test1; // variabile globale, instanţe de "toSay"

int main()
{ // la intrare, execută "startup code"; construieşte variabilele globale

    /* bloc vid (nici o instrucţiune proprie) */

} // la ieşire, execută destructorii

În programul redat mai sus, "struct" este cel din C++ - permiţând nu numai colectarea unor date, dar şi angrenarea în corpul structurii, a unor funcţii. Funcţiile din liniile 5 şi 9 sunt speciale: au acelaşi nume ca şi structura în care sunt ambalate şi au rolul de a construi şi respectiv, a "distruge" exemplarele structurii; distincţia necesară la nivel sintactic este asigurată prin prefixarea numelui în cazul destructorului, cu "~".

vb@vb:~/clasa11/OOP/Matrice$ g++ say.cpp -o say     Obţine executabilul
vb@vb:~/clasa11/OOP/Matrice$ ./say    Lansează în execuţie (începând cu "startup code") 
    Hello!  I am at address: 0x804a0d4    Construieşte instanţa "test"
    Hello!  I am at address: 0x804a0d5    Construieşte instanţa "test1"
    Goodbye!  Destructor: execută this -> ~toSay() unde this indică "test" (0x804a0d4)
    Goodbye!  Destructor: execută this -> ~toSay() cu this indicând "test1" (0x804a0d5)
vb@vb:~/clasa11/OOP/Matrice$ 

Calea de lucru specificată (clasa11/OOP/) sugerează că avem aici o lecţie (introductivă, sau anticipativă) de "Elemente de OOP" (din programa şcolară pentru clasa a XI-a).

Apariţia pe ecran a mesajelor redate mai sus - cu toate că main() nu conţine nicio instrucţiune - se datorează faptului că C/C++ prevede ca înaintea punerii în execuţie a funcţiei principale main(), să se execute o secvenţă de instrucţiuni denumită startup code; aceasta are între altele, rolul de a aloca şi iniţializa toate variabilele globale - însemnând în cazul de faţă, executarea constructorului (liniile 5-7) pentru variabilele "test" şi "test1" din linia 14.

Adresele asociate obiectelor "test" şi "test1" sunt în cazul de faţă consecutive (0x804a0d4, respectiv 0x804a0d5) şi "nu conţin nimic", dat fiind că structura "toSay" (liniile 3-12) nu prevede câmpuri de date, ca membri; aceste adrese sunt folosite intern pe parcursul execuţiei pentru a lega instanţele respective de funcţiile membre ale structurii.

Funcţiile membre există "într-un singur exemplar", fiind partajate de toate instanţele; dar pot fi invocate din fiecare instanţă creată, datorită faptului că fiecare funcţie membră a structurii este dotată intern cu un parametru suplimentar (de tip "pointer") numit this, tocmai pentru a indica obiectul din care este apelată acea funcţie, în cursul execuţiei. Astfel, se creează posibilitatea ca funcţiile respective să acţioneze asupra datelor curente specifice instanţei din care sunt apelate (ceea ce am ilustrat în comentariile de la mesajul "Goodbye").

În loc de "struct", putem folosi class (întâlnit şi în Python, în PHP, sau Java) - având în plus faţă de cele evidenţiate mai sus posibilitatea restricţionării accesului din exterior la anumiţi membri interni ai obiectului, precum şi posibilităţi de extindere ierarhică a claselor de obiecte. Dar în cazul de faţă este suficient să folosim struct, dat fiind că "toSay" nu are de ce să prevadă altceva (date sau funcţii membre private, sau publice), decât un constructor şi un destructor.

vezi Cărţile mele (de programare)

docerpro | Prev | Next