A három const

Czirkos Zoltán · 2019.02.27.

Mit jelent a const a tagfüggvényeknél? A paramétereknél, a visszatérési értéknél? Milyen programozási hibák ellen véd?

Adott az alábbi kódrészelet:

class A {
    B const & fv(C const & y) const;
};

Van ebben három const minősítő is. Vajon melyik mire vonatkozik? Melyik mit akadályoz meg?

Konkrétabb példa

Tekintsünk egy mátrix osztályt. A mátrix valós számokat tartalmaz:

3.1417 6
4.51.293
-230 4.5

Indexeléskor meg kell adnunk a sor- és az oszlopindexeket is. Viszont az indexelő operátornak csak egy paramétere lehet. Ezért úgy döntünk, hogy a sor- és oszlopindexet betesszük egy objektumba. A mátrix 1. oszlopa 2. sorának eleme így érhető el:

std::cout << m[Index(1,2)];

Ez formailag tökéletesen megfelel a legfölső kódnak. Az osztályaink tehát így néznek ki (csak a lényeges részek):

struct Index {
    int x, y;
};

class Matrix {
    int szelesseg, magassag;
    double* adat;

    double const & operator[] (Index const & i) const {
        return adat[i.y * szelesseg + i.x];
    }
};

Kérdés tehát most: melyik const mit csinál, milyen programozási hiba ellen véd? Az alábbi kódrészlet foglalja össze a hibákat:

class Matrix {
          /* 3 */                   /* 1 */    /* 2 */
    double const & operator[] (Index const & i) const {
        i.x = 32324;    /* 1 */
        this->szelesseg = 6789; /* 2 */
    }
};

Matrix const m;
m[Index(3,2)] = 3.14;   /* 3 */

A paraméter konstans: operator[] (Index const & i)

Az i paraméter konstanssága akadályozza azt meg, hogy a tagfüggvényben az i-nek, vagy az i bármelyik tagváltozójának értéket adjunk. Tehát ezek a sorok fordítási hibát eredményeznek:

class Matrix {
    double const & operator[] (Index const & i) const {
        i.x = 32324;    /* hiba */
        i = Index(3,4); /* hiba */
    }
};

Ez a konstans azért kellett, hogy az indexelésnél, az m[Index(3,2)] kifejezésben a temporális objektumot át tudjuk venni paraméterként. Ahhoz konstans referencia kell, vagy érték szerinti paraméterátvétel. (Az indexelő objektum egyébként olyan kicsi, két egész szám adattaggal, hogy jobb lenne érték szerint átvenni.)

A tagfüggvény konstans: operator[] (...) const

A tagfüggvény konstanssága miatt a this egy Matrix const * típusú pointer lesz. Emiatt a mátrix objektum adattagjait nem lehet változtatni. Az alábbi sorok fordítási hibát eredményeznek:

class Matrix {
    double const & operator[] (Index const & i) const {
        this->szelesseg = 6789; /* hiba */
        this->data = NULL;      /* hiba */
    }
};

Ez jogos is, hiszen az indexelés által egy konstans mátrix nem változhat meg, nem lehet se szélesebb, se keskenyebb.

Fontos megjegyezni, hogy bár a data pointer ilyenkor konstans, továbbra is double * a típusa. Tehát a konstansság kiterjed magára a pointerre, mint adattagra, de már nem terjed ki a pointer által mutatott elemekre. Egy ilyen hibától a tagfüggvény konstanssága nem ment meg minket:

class Matrix {
    double const & operator[] (Index const & i) const {
        this->data[0] = 3.14;   /* az ellen nem véd */
    }
};

A visszatérési érték konstans: double const & operator[] (...)

A visszatérési érték referencia szerint mutat rá a mátrix objektum által kezelt tömb egy elemére. Ha a mátrix konstans, akkor a benne tárolt számoknak sem szabad változniuk. Ezt olyan módon tudja biztosítani a mátrix, hogy indexelés hatására konstans referenciát ad vissza a tárolt valós számra. Így az alábbi kód fordítási hibához vezet:

Matrix const m( /* ... */ );

m[Index(3,2)] = 3.14;

Mégpedig azért, mert az értékadás bal oldalán bár referencia van, de az a referencia egy konstans számra vonatkozik, aminek nem lehet értéket adni.