A rövid cikkecske az elméleti C++ gyakorlati oldalait fogja bemutatni úgy, hogy egy nagyon egyszerű C++ programon fog elmagyarázni egy pár dolgot, melyek megértése első látásra talán gondot okozna a C++ t most kezdő programozóknak. Természetesen, ezt nem azért mondtam, hogy most elvegyem ezen olvasói réteg kedvét a cikktől, hanem azért, hogy ők olvassák el kétszer is a forráskódot, mielőtt elméleti eszmefuttatásokba kezdenének a kódról J.
Elsősorban, talán azt az eléggé ismert tényt boncolgatnám, hogy mit is térít vissza egy konstruktor. Persze, minden C++ programozó tudja, hogy a konstruktor egy speciális függvény, amely az égvilágon semmit nem térít vissza. Hogy picit megingassam a hitükbe, hát tekintsük a következő egyszerű C++ programot:
#include <iostream.h>
class A
{
public:
A(const A& a)
{
cout << "Copy Constructor" << endl;
}
A()
{
cout << "Constructor" <<endl;
}
~A()
{
cout << "Destructor" << endl;
}
void DoSomething()
{
cout << "DoSomething()" << endl;
}
};
int operator + (int x, A a)
{
return x;
}
int main()
{
A().DoSomething();
int x = 0;
x = x + A();
cout << x <<endl;
}
Látván, hogy egy konstruktor függvény után, mely létrehozza az A osztályt rögtön függvényhívás következik, felmerülhet a kérdés: „Mégis, nem térít vissza az a konstruktor valamit?”. A válasz nagyon egyszerű: a konstruktor tényleg nem térít vissza semmit. Annak ellenére, hogy itt „úgy tűnik” hogy a konstruktor egy A osztályból példányosított objektumot fog visszatéríteni, hát nem: a C++ automatikusan létrehozta az A osztály objektumot a konstruktor hívás után, és már ennek az objektumnak hívja meg a DoSomething metódusát (automatikus típuskonverzió).
A következő, szintén nem szokványos sor, az az, ahol a konstruktor függvényt hozzáadjuk az x értékhez. Hát itt megint a fenti eset áll fenn, amikor automatikusan létrehozásra kerül (és törlődik is) az objektum. Amennyiben nem definiáltunk olyan operatort, amely egy intet, és egy A objektumot vár el paraméterként fordítási hibával leáll a compiler.
Felmerülhet az is, hogy ha esetleg referenciát vár el az operátor, akkor miért keletkezik fordítási hiba? Mert temporális objektumokat nem lehet referenciával átadni.
A következő nagy témakör, amiről lehet beszélgetni, az a konstruktorok meghívását ölelné fel. A legelső konstruktor hívás az A().DoSomething sorba kerül végrehajtásra, viszont ugyanabba a sorba, már a destruktor is meghívódott, hisz csak temporális változóról van szó (A temporális változó még csak nem is lokális változó, hisz nem tudjuk implicit módon elérni).
A következő konstruktor hívás az x = x + A(); sorba lesz, amikor is létre kell hozni a temporális objektumot, és meg kell hívni a + operatort, aminek egyik paramétere a temporális objektum. És itt történik a Copy konstruktor meghívása, amelyet felüldefiniáltunk.
Visual C++ (6) esetén viszont tapasztaltam egy pár érdekességet:
A temporális objektum esetében nem hívta meg a Copy konstruktort, amikor az operátor függvény paraméterezését oldotta meg.
Az operátor definiálásában elfogadta a referenciát is. A HP-UX aCC fordítója nem fogadta el a referenciát. Helyesebben mondva: nem fogadta el, hogy egy temporális objektumot adjak át olyan helyre, ahol referenciát vart volna el. Természetesen az (A&) típuskonverziót használva, ezt is meg lehetett oldani, viszont ezen esetbe már nem hívta a Copy konstruktort.
A fent bemutatott technológia nagyon hatékony programozási módszer, hiszen kiküszöböli a felesleges változók használatát. Például vegyük a következő kódot (a Circle konstruktor függvénye x, y, z, r paramétereket vár el):
Circle circ(0,0,0,1);
int x = circ.Area();
Ezt a következő kódrészlettel tudjuk helyettesíteni:
int x = Circle(0,0,0,1).Area();
Megspóroltunk egy változót, és a memóriafoglalást is optimalizáltuk, hisz az első esetbe a circ objektum a memóriába marad, amíg a program be nem fejeződik, de a második esetbe a temporális objektum automatikusan törlődik.
A cikk második témája a függvények paramétereinek kiértékelése lesz. Mint tudjuk, a C++ (az összes többi programozási nyelvhez hasonlóan) a vermen tárolja az összes paramétert, az viszont nem világos, hogy ha egy függvényhívás paraméterezésében változik a paraméter értéke, akkor melyik érték kerül rá a stack-re.
A következő programocska egy feladat volt egy versenyen, az volt a kérdés, hogy mit fog kiírni a program:
#include <iostream>
void function(int a, int b, int c)
{
cout<<a<<b<<c;
}
int main()
{
int i = 0;
function(i++, i++, i++);
cout<<i;
}
Egyesek tippeltek arra, hogy meg fog jelenni: 1, 2, 3, 3. Mások meg arra tippeltek, hogy 0, 0, 0, 3. És hogy kinek volt igaza? Mindenkinek, ugyanis a C++ standard ebbe a pillanatba nem tartalmazza, hogy a paraméterek értékét mikor fogja kiértékelni: mielőtt, vagy miután a stack-re rárakta a megfelelő paramétert. Ezért egyes compilerek teljesen más eredményt adnak. Érdemes kísérletezgetni, sok sikert.