C++ kérdések

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.

Deák Ferenc - deak.ferenc@miskolc.evosoft.hu