C++ const: szereted, vagy gyűlölöd?

 ( Joejszaka | 2010. március 18., csütörtök - 18:26 )

C++ newbie koromban ismertem egy nagyon jó programozót, nevezzük most Zsoltnak.

Azt mondta nekem ez a Zsolt, hogy ő a C++-ban a const kulcsszót szereti a legjobban. Fiatal voltam, hittem neki. Szigorúan mindig odaírom azóta is a a const metódusok mögé a kedvenc kulcsszavunkat, jelezve, hogy ez az eljárás nem változtatja meg objektumunk állapotát.

Aztán jöttek a szopások:
iterator, const_iterator, referenciaszámolt osztályok, map-ek, duplikált const és nem cont metódusok, mutable kulcszó ízléstelen használata.

Rájöttem, hogy enyhén szólva is könnyebb lenne az életem egy olyan C++-szal, amiben nem szerepel a const kulcsszó.

Szerintetek legalább optimalizálásban reálisan hoz valamit, ha már szopjuk a répát miatta?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

jó az.
kell a minél több statikus típusellenőrzés, mert ez szoktat rá a szigorú gondolkozásmódra.

Abszolut kell. Aki programozott mar mas nyelvben is (komolyabb dolgokat, nem 92. webshopot), az tudja, hogy mennyire hianyzik a Javaban vagy a C#-ban. Sokkal inkabb ondokumentalo a kod.

Ha problemaid vannak a const_iteratorokkal es a tobbiekkel, akkor valami nincs jol kitalalva a kododban, gondolkozz meg rajta.

----------------------
while (!sleep) sheep++;

c# -ban is van const, illetve javában final.
Vagy azok eltérnek a c++ ostól?

Ezekben a nyelvekben konstans valtozokat deklaralhatsz, de pl. konstans visszateresi erteket vagy metodust nem.

----------------------
while (!sleep) sheep++;

c++ -ban kétféle értelemben is létezik a const:

const int* p=0; // konstansra mutató pointer
int* const p=0; // read-only pointer, ez felel meg a java finaljának

Ne feledkezzünk meg a konstans tagfüggvényekről sem, amelyek konstans objektum esetén is hívhatók!

--
Debian - The "What?!" starts not!
http://nyizsa.uni.cc

Ami ugyanakkor nem harmadik eset, hanem a két fő koncepció közül az első, mert effektíve csak const T* / const T& változókon keresztül keresztül láthatsz valamit konstans objektumnak.

És az melyik, hogy
const Osztaly foo;
?

--
Debian - The "What?!" starts not!
http://nyizsa.uni.cc

Keletkezik egy normál objektum, amit a foo-n keresztül ugyanúgy érsz el, mintha egy const& -en keresztül érnéd el. Ugyanakkor mivel a referenciát nem állíthatod át másra, ezért ez egyszerre mindkettő; ilyen szempontból a const T* const -hoz hasonló eset.

const T cx;
T& x=const_cast<T&>(cx);
x.i=5;

Szerk.: végülis ez a kód irreleváns, de már nem törlöm ki.

Mindegy, nem is erre akartam kilyukadni, hanem hogy ha vannak ilyen tagfüggvényeid, hogy

int foo() {return 42;};
int bar() const {return 42;};

akkor a
cx.foo();
nem fog lefordulni.

--
Debian - The "What?!" starts not!
http://nyizsa.uni.cc

Oké, bocsi hogy diszkombobuláltam a szálat :)

Nem egészen értem, hogy itt mire szerettél volna rámutatni. Arra, hogy a const elérés "mögött" valójában mindig egy normál objektum lapul, aminek a konstans mivolta csak azt jelenti, hogy éppen csak olyan neveken lehet hivatkozni rá, amik konstansként vannak definiálva? Mert tudtommal ez nem igaz. A szabvány szerint a harmadik sorban szereplő értékadás végrehajtása nem definiált állapotot eredményez (== implementáció függő), ha T::i nem mutable-ként van definiálva. Ez azért van így, mert a végrehajtási környezet támogathatja a konstans változókat tartalmazó memóriaterületek védelmét. Az adott implementáció kezelheti úgy az ilyen eseteket, hogy simán elkönyveli mégis írhatónak az objektumot (tudtommal csak ilyen implementációk vannak), de lehet, hogy rendszerszintű védelmi hibát generál (ilyenről nem tudok).

Persze ha a T::i mutable-ként van definiálva, akkor a kód teljesen jó, csak szokatlan.

(Remélem valaki szól, ha baromságokat írtam, mert még csak tanulgatom a nyelvet és szeretnék okosodni). :)

szerintem jol mondod, azzal a kiegeszitessel, hogy sok olyan implementacio van ami bizonyos (pl. globalis) konstans valtozokat nem irhato helyre tesz - pl. nalam a 64 bites ubuntu is segfault-ol a fenti peldara, ha a cx globalis valtozo.

sot, beagyazott szoftvereknel kulonosen jol johet a const valtozo, mert ezeket a toolchain be tudja tenni kizarolag ROM-ba (pl. a mikrokontroller beepitett flashebe), es akkor a valtozo - azon tul, hogy csak olvashato lesz - nem hasznal semennyi RAM-ot, ami keves RAM eseten letfontossagu.

- Use the Source Luke ! -

A mutable-t ne keverjük ide.

Ha az objektum const-ként van definiálva, akkor a const_cast segítségével történő módosítás nem definiált eredményre vezet. Ez azért van, mert ahogy denes is mondta, a const-ként definiált változó kikerülhet olyan memóriaterületre ami védett az írással szemben.

Feltehetnétek a kérdést, hogy miért van const_cast egyáltalán?
Azért, mert ha a változó eredetileg nem const, de mondjuk egy fv const referenciaként vagy mutatóként kapja meg, akkor a const_cast "biztonságos" (azaz jól definiáltan működik).
Más kérdés, hogy aki ilyen esetben használja, annak nem szabadna C++ elé ülnie. :)

Tehát akkor mi a francért van const_cast?
Egyrészt, mert a C-ben van ilyen konverzió, tehát hiányzott a *_cast-ok közül.

Illetve van egy use-case ahol kellhet: egy const tagfüggvényben.
Bizonyos felfogás szerint a const a tagfv definíciójában elsősorban azt jelenti, hogy az objektum _logikailag_ nem változik. Ami az esetek nagy százalékában azt is jelenti, hogy fizikailag sem. Ezért a fordító szerint ez az alapértelmezett. De ettől el lehet térni:

Példa: Van egy (valamilyen) adatbázist reprezentáló osztályunk. Logikus elvárás, hogy annak lekérdező fv-e const legyen. Ezzel nincs is semmi baj.
De tegyük fel, hogy a program lassú, mert a lekérdezés lassú (lassú io, pl net), viszont ugyanaz az adat sokszor kerül lekérdezésre.
Megoldás: cache. Persze úgy, hogy ne kelljen az egész programot átírni.
Azaz a lekérdező fv-ben kéne megoldani a felhasználó számára láthatatlanul, de közben a lekérdező fv const kell, hogy maradjon. Na ekkor kell a const_cast, hogy cache-t reprezentáló adattagot mégis lehessen módosítani...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Értem, tehát nem a harmadik sorban csúszik félre a dolog, hanem már a másodikban, a cast-olásnál. Viszont amit példaként írtál, azt nem egészen értem. Írok egy példát, ahol a dátum string alakját cache-eljük:

class Date
{
public:
    //...
    std::string toStr() const;
private:
    //...
    void computeStrRep();
    std::string _strRep;
    bool _isStrValid;
};

std::string Date::toStr() const
{
    if (_isStrValid)
        return _strRep;

    Date *obj = const_cast<Date*>(this);
    obj->computeStrRep();
    return _strRep;
}

Date d1(2010, 3, 20);
const Date d2(2010, 3, 20);
d1.toStr(); // ez jó
d2.toStr(); // nem definiált viselkedés

Ez éppen olyan rossz, mint a kiindulási példa. A Stroustrup-könyvben direkt ki is van emelve, hogy ha valódi const objektumra hívjuk meg a fenti const tagfüggvényt, akkor ez a this-móka nemdefiniált. Erre jó a mutable módosító:

class Date
{
public:
    //...
    std::string toStr() const;
private:
    //...
    void computeStrRep() const;
    mutable std::string _strRep;
    mutable bool _isStrValid;
};

std::string Date::toStr() const
{
    if (_isStrValid)
        return _strRep;

    computeStrRep();
    return _strRep;
}

Vagy valami egészen másra gondoltál, és akkor fölöslegesen írtam ezt az egész novellát. :)

nem a harmadik sorban csúszik félre a dolog, hanem már a másodikban, a cast-olásná

nem, szerintem felreertetted tr3w mondatat. a harmadik sor a hibas ahogy korabban mondtad, legalabbis a szabvany ezt tamasztja ala a "Const cast" alfejezetben (5.2.11 nalam, 4. pont), sot van egy nagyon hasonlo pelda a "The cv-qualifiers" alfejezetben (7.1.6.1 nalam) - vegulis az elozo hozzaszolasod pont ezt a "The cv-qualifiers" alfejezetet irja le, ugyanugy megemlitve a mutable-t, de valoszinuleg nem veletlenul. :)

- Use the Source Luke ! -

Nem én hordom a végtelen igazság sapkáját, és most nem is olvastam utána, de szerintem a mutable nem oldja meg a problémát, a d2.toStr() továbbra is nemdefiniált...

A mutable azt mondja a fordítónak, hogy az adott változót minden felhasználásakor olvassa ki a memóriából, és ne optimalizálja ki, sőt, még csak regiszterben se tárolja el hosszabb időre.
Ezért a régebbi példánál hiába const az az int, a mutable megakadályozza a fordítót abban, hogy fordítási időben behelyettesítse a kifejezésekbe, ezért a const_cast-os trükközés működik.

De _szerintem_ nem akadályozza meg a fordítót abban, hogy azt a változót egy csak olvasható memóriacímre rakja, így a const_cast-os írás eredménye továbbra is nemdefiniált.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

az a volatile, nem a mutable

- Use the Source Luke ! -

Valóban megkevertem a dolgokat, bocsi...

Akkor viszont a példámhoz nem kell const_cast... Mégis csak azért van, hogy mindent megtehess amit C-ben...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Közben eszembe jutott egy helyzet ahol hasznos a const_cast, bár az is inkább a volatile kulcsszó esetében és nem const-tal.

Viszont nem írom le, mert megtette más. :)
http://www.drdobbs.com/cpp/184403766

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Ja igen, ha - ahogy denes írta feljebb - véletlenül összekeverted a volatile-lal, akkor már értem, hogy mit akartál mondani. :)

De nincs const fv, const paraméter...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

+1

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

+1

Öndokumentálás +1.

Viszont:

"Ha problemaid vannak a const_iteratorokkal es a tobbiekkel, akkor valami nincs jol kitalalva a kododban, gondolkozz meg rajta."

Nyilván az én hibám, hogy hányingerem van, ha le kell írni, hogy const_reverse_iterator??? :)))

Ezek a dolgok, amiket említettem, egyszerűen kényelmetlenek, és lassítják a fejlesztést.

Igazából megint kibukott belőlem, hogy a C++ nem eszköz, hanem életforma.

Miért olyan nehéz leírni, hogy con<ctrl+space>r<ctrl+space>? :)

Azt sem értem egyébként, hogy miért zavar ez néhány embert ennyire: biztos én dolgozom nagyon speciális területen, de az elmúlt 5 évben talán 2x írtam le azt, hogy const_reverse_iterator...

Egyébként meg auto lesz a te barátod. Illetve gcc alatt akár már most is...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

en tegnap irtam le eletemben eloszor :D pedig kodolok mar egy ideje...

"kell a minél több statikus típusellenőrzés, mert ez szoktat rá a szigorú gondolkozásmódra."

:)

Vicces érv ez C++-nál, ahol egyébként minden ellenőrzést ki lehet kerülni egy kis akarattal.

A szigorú típusellenőrzés azért kell, hogy sok hiba/elírás már fordítási időben kiderüljön.

"Vicces érv ez C++-nál, ahol egyébként minden ellenőrzést ki lehet kerülni egy kis akarattal."
Pont ez a lényeg, hogy nem köti meg a kezed (ha tudod mit csinálsz csináld), de szól, ha csak véletlenül csinálsz furcsaságokat...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Szvsz const-val elég sok hibára rá lehet jönni, méghozzá compile time-ban, ami azért előnyös.

Nem mindegy, hogy:

if(x == 5)

vagy

if(x = 5)

Jó persze, ez sem használható mindenhol, de ez csak egy egyszerű példa volt a többi közül. :)

-- | --
|raczman| if theres one thing i believe in, is God and mr. Schmidt's coding

Huh, ez milyen mókás működést tud eredményezni, csak egy elfelejtett egyenlőség jel :) szerintem a legjobb fejemrecsapok szituációkat köszönhetem ennek :)

Erre azért általában dobnak warning -ot a C és C++ fordítók.
És már sok éve dobnak.

regebben valamelyik kernelben is volt ilyen turpissag

/* bocs az esetleges helyesirasi hidakert */

Erre a szép nyelvfüggetlen (pontosabban const-független) megoldás az if( 5 == x ) alakú kifejezések használata.

Nezegettem ezt is, de valahogy nem all ra a kezem. Megszoktam mar, hogy azt irom le, amit gondolok, es en ugy szoktam gondolni, hogy "ha x egyenlo ottel, akkor" vagy "ha x 5, akkor" mardpedig itt nem olyan sorrendben vannak a dolgok. Persze, en koca koder vagyok... :-)
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

(törölve, benéztem)

Persze, igy van, szoktatni kell ra az embernek az agyat. de hat nem azt kell kodolni, amit gondolsz, hanem azt, amit vegre akarsz hajtani a szamitogeppel. Egyszeru rendszer ez, nincs benne semmi magia, csak azt csinalja, amit mondanak neki.:)

Eppenhogy ezt szeretnem vegrehajtatni, de megse ezt kell irnom. De vegulis mindegy, amig -Wall -ra warningol, addig nincs baj.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Arról nem is beszélve, hogy az összes optimalizálással foglalkozó leírás ezt a kifejezés-alakot javasolja.

Nekem elég, ha egyet mutatsz. Kizártnak tartom, hogy ez bármit is számítana egy normális fordítónál...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Sírvafakadnék, ha ilyen kódot kéne nézegetnem, bár ez nyilván megszokás kérdése...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Én az Effective C++ könyv olvasása után szoktattam rá magamat, azóta nincs olyan, hogy lehagynám valahonnan, ahol csak lehet kiírni, még "gyorsan kipróbálok valamit" progiknál is.

Össze sem tudom számolni, hogy hány alkalommal mentett ez meg a még sokszor nagyobb szívásoktól. Az elején ki kell járni a rögös utat, hogy forduljon minden, de ha belejön az ember, már a kódoláskor tudja, hogy mit szabad és mit nem. Ha nincs const sehol, mindenhol mindent szabad, még azt is, amit nem.

const++

"mindenhol mindent szabad, még azt is, amit nem"

:D

(

Optimalizálás (g++ esetén):

  • const int x=5 esetén inlineolja a konstans értékét, szóval megspórol egy memóriaműveletet :-)
  • const member-függvény meghívása ugyanúgy történik mint a nem consté
  • const-member függvény assembly kódja ugyanaz mint a nem consté
  • Szóval szerintem futásidőben nem nyersz vele, inkább a kód konzisztenciáját növeli a const-ok használata, meg jópár debug orát spórolhatsz ...

    személyes kedvencem consttal kapcsolatban:


    #include <cstdio>
    int main() {
        const int x=1;
        *(const_cast<int*>(&x))=0;
        printf("%d\n", x);
        printf("%d\n", *(const_cast<int*>(&x)));
    }

    bevallom, én nem találtam el, hogy mit fog kiírni, bár mentségemre szolgáljon, hogy nem programozok c++-ban ...

    "const int x=5 esetén inlineolja a konstans értékét, szóval megspórol egy memóriaműveletet :-) "

    Igazából ha egy ilyen "int x = 5" -l triviális műveleteket végzel, azt minden tisztességes fordító kioptimalizál.

    ----------------
    Lvl86 Troll

    Huh, ilyenkor örülök, hogy nem szoktam "kreatív" utakon járni.

    ha már megerőszakoljuk ezt a const dolgot, akkor így nézd meg :
    volatile const int x=1;

    :-)

    megnéztem! olyankor úgy viselkedik mintha sima int-ként definiálnád :-)

    egyébként a fentiben nekem az volt vicces, hogy a const változóknak is lefoglalja a helyet, és még inicializálja is, de csak akkor használja, ha lecastolod róla a const módosítót, egyébként simán inlineolja a const értéket ...
    (én meg naívan azt hittem, hogy ha a fenti módon felül tudom írni a const változó értékét ... )

    ja és mielőtt valaki elszörnyed a fenti const_cast-os baromságot sose használnám "élőben" ...

    Én nem látom a szopásokat legalábbis ennyiből amit leírtál.

    Optimalizálásban gyakorlatilag nem számít semmit, ellenben egy csomó egyébként futási időben bajt okozó hibára fény derül fordítási időben.
    Azaz teljesen ugyanaz az értelme, mint az egész C++ szigorú típusosságának.

    "...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

    Ha a C++ szigorúan típusos lenne, akkor az if() feltételébe csak logikai típusú kifejezést lehetne írni, így az if(x = 5) nem lenne megengedett.

    Az x=5 kifejezés eredménye az mindig true nem?

    Nem, az x=5 kifejezés eredménye 5.
    Az x=0 -é pedig nulla.
    És ugye a nulla az false, nemnulla pedig true. :)

    De egyébként ilyen értékadásokra warning -ot dob a fordító, szóval ha elolvassa az ember a warningokat (és ugye elolvassa), akkor ebből ma már gond nem igen lehet.

    A C++-t pedig szigorúan típusosnak szokták hívni, mert objektumok esetében csak olyan konverziók történhetnek meg amik az öröklődés miatt értelmesek, vagy amit te magad engedélyezel.

    Az if-nél meg az történik, hogy az int automatikusan konvertálódhat bool-ra.
    Hogy ez jó, vagy nem jó, azon lehetne vitatkozni, de az tuti, hogy a C kompatibilitás miatt kellett...

    "...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

    +1

    Bar, sztem Javaban se lehet oroklodesi agon kivulre konvertalni. Szoval ilyen alapon akar az is lehetne erosen tipusos, aztan megse az.
    --

    ()=() Ki oda vagyik,
    ('Y') hol szall a galamb
    C . C elszalasztja a
    ()_() kincset itt alant.
    

    Én a Java-t annak tartom.
    Ahogy nézem nincs egyértelmű válasz, bizonyos szempontból még a C is az:
    http://en.wikipedia.org/wiki/Strongly_typed_programming_language

    Maximum egy sorrendet lehetne felállítani:
    C < Java <= C++

    "...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

    "Én nem látom a szopásokat legalábbis ennyiből amit leírtál."

    A fő szopás akkor van, ha bevezetünk valami nyalánkságot, pl. ref-számolást.
    Ilyenkor vagy nem írunk const-okat, vagy becsomagoljuk az osztályainkat sima, illetve const-os változatba, mint ahogy az stl csinálja az iteratorral és a const_iteratorral. Ez dupla munka. Ezért nem szeretem.
    Tény, hogy olvashatóbbá teszi a kódot, cserébe többet kell kódolással foglalkozni, a teljesítményen meg nem látszik.

    Lehet, hogy ebből a mondatból nem látszik, ha nagyon kell, írok majd példát, csak hosszú lesz.

    Azt hiszem a ref-számolás tud a legjobban betenni a const rendszernek, főleg, ha viszonylag bonyolult objektumot számolunk refesen, pl. egy fa elemei vannak ref-számolva.

    Végiggondoltuk a kollegákkal többször is, mindenképpen szopóág. Vagy dupla meló, vagy csúnya kód. Ezért utálom a const-ot. :)))

    Most hirtelen nem látom át, de bizonyára vannak olyan esetek, ahol megnehezíti a helyzetet. Ilyenkor el kell dönteni, hogy megéri-e elhagyni a const-t, és hogy van-e olyan gárda, aki emellett hibamentesen tud kódolni, továbbá a kifelé látszódó interfész megfelelően van megtervezve.

    Még mindig nem értem hol a folyamatos szívás. Leginkább azért nem, mert sem a refszámlálós pointereknél nem kell külön const változat (lásd boost::shared_ptr), sem "belső" refszámlálós osztályoknál (lásd Qt osztályainak fele).

    Pedig tényleg érdekelne...

    "...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

    C++-ban nagyon sokat lehet tanulni a programozásról, legfőképpen a nyelvi kifejezésekben. De egy idő után könnyebb az ember élete C++ nélkül.

    --
    The Net is indeed vast and infinite...
    http://gablog.eu

    A C++ olyan, mint a drog, teljesen sosem lehet elfeledkezni rola. Es neha azert jo hogy van...
    --

    ()=() Ki oda vagyik,
    ('Y') hol szall a galamb
    C . C elszalasztja a
    ()_() kincset itt alant.
    

    Szvsz ez egy szimpla marhaság.

    Nem véletlen a const létrejötte. Ha valaki nem tudja használni, ne használja. Ha nem tetszik neki, ne használja.

    Rájöttem, hogy enyhén szólva is könnyebb lenne az életem egy olyan C++-szal, amiben nem szerepel a const kulcsszó.
    :)))))
    Lehet, hogy sokat segítene neked, ha nem írnád le. ?

    Egyébként meg:
    http://hup.hu/node/33758#comment-298331
    ---------------------------------------------------
    Talisker Single Malt Scotch Whisky aged 10 years :)