Deri tani, të gjitha variablat që i kemi përdorur janë variabla automatike. Ky term (i përdorur rrallë) tregon se variablat lokale krijohen kur të arrihen në funksion dhe asgjësohen (shkatërrohen) kur më nuk janë në fushëveprimin e funksionit (p.sh., kur funksioni kthen vlerën me return). Ndonjëherë, objektet duhet të krijohen në mënyrë tjetër. Mënyra tjetër është alokimi dinamik i memories.
1.4.1. Operatori “new”
Objektet mund të krijohen në mënyrë dinamike duke thirrur operatorin “new” (angl. new - i ri, e re). Operatori new alokon memorien në mënyrë dinamike (gjatë ekzekutimit të programit) dhe kthen pointerin për në objektin e ri të krijuar. Si rezultat të “new” kemi pointerin që pointon në objektin e ri të krijuar. Programi 1.5, ilustron çështjet e përfshira në alokimin dinamik të memories. Sidoqoftë, shembulli është një përdorim “i varfër” i memories dinamike, pasi që do të duhej të përdoret një string automatik. Në këtë rast përdoret vetëm sa për të ilustruar alokimin dinamik në kontekst të thjeshtë. Aplikimi më i arsyeshëm do të paraqitet më vonë (në pikën 1.6.2).
Në programin 1.5, rreshti 9 krijon një string të ri në mënyrë dinamike. Vëreni se strPtr është pointer në tipin string, ashtu që vet string qaset përmes *strPtr, si është treguar në rreshtat 10-13. Kllapat janë të nevojshme në rreshtin 11 për shkak të rregullave të prioritetit (precedencës).
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 int main( ) 6 {
64
7 string *strPtr;
8
9 strPtr = new string( "hello" );
10 cout << "Stringu eshte: " << *strPtr << endl;
11 cout << "Gjatesia e tij: "<<(*strPtr).length( )<<endl; 12 *strPtr += " world";
13 cout << "Tani stringu eshte " << *strPtr << endl;
14
15 delete strPtr;
16
17 return 0; 18 }
Programi 1.5 – Ilustrim i alokimit dinamik të memories
1.4.2 Pastrimi i mbetjeve dhe fshirja
Në disa gjuhë programuese, kur objekti më nuk referohet, është subjekt i grumbullimit automatik “të mbeturinave” (angl. garbage collection – pastrim i mbetjeve, mbledhje mbeturinash). Programeri nuk duhet të brengoset për këtë problem (Është fjala për mos zënien e hapësirës në memorie, nga variablat/objektet të cilat më nuk janë të nevojshme në program). Mirëpo, C++ nuk e ka mekanizmin e grumbullimit automatik të mbeturinave. Kur një objekt që alokohet me new, nuk referencohet më tutje, atëherë duhet të aplikohet operatori “delete” (angl. delete-fshije) duhet të aplikohet në objekt (përmes pointerit). Përndryshe, memoria të cilën ai e konsumon humbet (deri sa programi të ndalet), gjë që njihet si “memory leak” (angl. leak-rrjedhje, pikim, humbje, etj.). fatkeqësisht, rrjedhjet e memories janë dukuri e shpeshtë në shumë programe në C++. Për fat të mirë, shumë burime të rrjedhjes së memories mund të largohen automatikisht, me kujdes, si do të shihet në vazhdim.
Një rregull e rëndësishme është që të mos përdoret new kur mund të përdoret variabla automatike. Variabla automatike pastrohet automatikisht (prandaj edhe quhet ashtu). Nuk duhet përdorur kurrë delete në një objekt që nuk është krijuar me new; përndryshe, do të rezultojë me një “shkatërrim” gjatë kohës së ekzekutimit. Operatori delete është ilustruar në rreshtin 15 (të programit 1.5).
1.4.3 “Stale” Pointerët, fshirja e dyfishtë dhe problemet tjera
Një arsye që programerët mund të gjinden në telashe gjatë përdorimit të pointerëve është fakti që një objekt mund të ketë disa pointerë të cilët pointojnë në të. Shqyrtoni kodin në vijim:65
//stringun e ri string *t = s; // t pointon aty, poashtu
delete t; // Objekti nuk është më
Askush nuk do t’i shkruante qëllimisht këta tre urdhëra njëri pas tjetrit, mirëpo supozoni situatën kur ata janë të shpërndarë në një kod të gjatë e në një funksion kompleks. Para thirrjes së delete, kemi një objekt të alokuar në mënyrë dinamike i cili ka dy pointerë që pointojnë në të.
Pas thirrjes së delete, vlerat e s dhe t (d.m.th., ku ata pointojnë) nuk kanë ndryshuar. Mirëpo, si është ilustruar në Fig. 1.17, ata tani janë “stale” pointerë. (angl. stale-bajat, i ndenjur, etj.). Stale pointer është pointeri vlera e të cilit më nuk i referohet një objekti valid. Pra, është fshirë objekti ku pointon pointeri. Dereferencimi i s dhe t mund të dërgojë në rezultate të paparashikueshme. Ajo që i bën këto gjëra veçanërisht të vështira është se, edhe pse është e qartë që t është stale pointer, fakti që edhe s është i tillë është më pak i dukshëm, sidomos nëse keni parasysh supozimin se këto urdhëra mund të jenë të shpërndarë në ndonjë funksion kompleks. Për më tepër, në disa situata, memoria që ishte e zënë nga objekti është e pandryshuar deri në një thirrje të mëvonshme të “new” për kërkesë të memories, gjë që mund të jep iluzionin se nuk ka ndonjë problem.
s
Hello
t
Fig. 1.17 Stale pointerët: pas urdhërit delete t, pointerët s dhe t tani pointojnë në një objekt që nuk ekzisto më; urdhëri delet s do të ishte fshirje e dyfishtë
ilegale
Problem tjetër është i ashtuquajturi “double-delete” (fshirja e dyfishtë). Ky problem ndodhë kur tentohet të fshihet i njëjti objekt më shumë se një herë. Kjo do të ndodhte nëse do të jepej në vazhdim urdhëri:
delete s; // fshirje e dyfishtë
sepse s është “stale” dhe objekti në të cilin pointon nuk është valid (nuk ekziston). Është mundëia shumë e madhe që do të paraqiten probleme të kohës së ekzekutimit (angl. run-time error).
Këto janë rreziqet e alokimit dinamik të memories. Duhet të jemi të sigurtë që kurrë të mos e thërrasim urdhërin delete më shumë se një herë për një objekt dhe atë vetëm pasi të mos jetë i nevojshëm. Nëse nuk thirret delete fare, edhe pse objekti më nuk është i nevojshëm, atëherë do të ketë rrjedhje të memories. Gjithashtu, nëse kemi variabël pointer dhe synojmë ta fshijmë me delete, duhet të jemi të sigurtë që objekti në të cilin pointohet ka qenë i krijuar me urdhërin
66
“new”. Kur kemi thirrje të funksionit prej funksionit, përcjellja e të gjitha elementeve bëhet më e vështirë.
Së fundi, pointerët mund të bëhen “stale pointer” edhe nëse nuk është bërë alokimi dinamik. Shqyrtoni kodin në programin 1.6.
Për ndonjë arsye me kuptim (përveq për ilustrim të gabimit), kemi funksionin stupit i cili kthen pointerin në string. Nëse funksioni stupit e thërret new për të krijuar stringun, atëherë thirrësi do të jetë përgjegjës për thirrje të delete. Në vend se të ngarkohet thirrësi, gabimisht kemi vendosur që funksioni stupid të përdorë një string automatik dhe të kthejë adresën e tij. Programi kompajlohet por mund të mos funksionojë, sepse përmbanë gabim. Problemi është se vlera të cilën e kthen funksioni stupid është pointer. Por pointeri është duke pointuar në s, e cila më nuk ekziston, sepse është variabël automatike dhe funksioni stupid (angl. stupid-torollak), veç ka kthyer me return (ka përfunduar punën). Kur të kthehet vlera pointer, të jeni të sigurtë që keni diçka në të cilën pointoni dhe se ajo ekziston edhe pasi kthimi (return) të jetë kompletuar.
1 string *stupid( )