C++ ja enum

Väljavõte proekspert.it newsgroupist:

Eile ühel kolleegil siin tekkis küsimus, kus esinesid mõisteted “enum”, “C++”, “standard” ja “porditav”. Küsimus on üleüldine tegelikult. “ISO C++ committee” kannib kah enumiga minuteada iga kord kui kokku tuleb. Ma ei tea, kas see on just parim koht aga viskan siia oma momendi seisukohad sellel teemal. Keda ei huvita, ei pea lugema.

enum-i kasutamine täisarvkonstantide grupi deklareerimiseks teeb koodi loetavamaks. enum-i kasutamine muutuja tüübina annab kah mõningaid eeliseid. Aga … toob kaasa ka mõned riskid, mis alati kõigile ilmselged pole.

Üks probleem on see, et enumeraatori väärtus konverteerub vaikselt täisarvuks. See probleem jätab enamuse suht külmaks; ikkagi parem kui #define konstant. Häda on kaudne. Kuna enumi väärkasutus annab erroreid siis algajal progejal võib tekkida põhjendamatu tunne, et enum, kui asi on läbimõeldud, turvaline ja korralik. See toob kaasa hooletuse ja siis hämmingu … et miks küll saab eri komplektide enumeraatoreid omavahel võrrelda (ilma hoiatusteta).

Teine probleem on see, et enum tüübi all peidus olev täisarvutüüp on standardi järgi täitsa implementation-defined ja kasutaja ei saa seda standardipäraselt regullida. Osad kompilaatorid sisaldavad selleks ebastandardseid laiendusi (nende kasutamine teeb asja aga veelgi hullemaks). See tuleb nii mõnelegi ootamatult ja ebaintuitiivselt. Kui keegi plaanib erinevate kompilaatoritega tehtud programmide/dll-de vahel C++ enumeid liigutada … siis paraku. Need asjad (standardi järgi) ei peagi ühilduma.

Implementation-defined asi põhjustab ka äärolukordades implementation-defined käitumise. Järgnevas näitekoodijupis loodab naiivne kasutaja et U suffiks laseb kompilaatoril aru saada, millist täisarvutüüpi ta loodab ja siis vaatab, et mis ta ka sai. Et asi tunduks emotsionaalselt dramaatilisem siis võrdleb ta noid enumeraatoreid veel -1 ga (see ei pea vist ettemääratud tulemust andma aga kompilaatorid võiksid pisut möliseda küll).

 
enum E 
{ 
    E1 = 1, 
    E2 = 2, 
    Ebig = 0xFFFFFFF0U 
};   
 
int main() 
{ 
    cout << "    Ebig = " << Ebig; 
    cout << "    E1 ? -1 = " << ( E1 < -1 ? "less" 
                                : E1 > -1 ? "greater" 
                                :"equal" ); 
    cout << "    Ebig ? -1 = " << ( Ebig < -1 ? "less" 
                                  : Ebig > -1 ? "greater" 
                                  : "equal" ) << endl; 
}

See kood kompileerub C++ kompilaatorite peal sedasi:

Borland 5.5.1: vaikselt
Digital Mars 8.38: vaikselt
Comeau 4.3.3 (EDG 3.3): hoiatab: integer conversion resulted in a change of sign
gcc 2.95.3: hoiatab: comparison between signed and unsigned
gcc 3.3.2: hoiatab: comparison between signed and unsigned integer expressions
Metrowerks Code-Warrior 8.3: vaikselt
Microsoft Visual C++ 6.0: vaikselt
Microsoft Visual C++ 7.1: vaikselt
Microsoft Visual C++ 8.0: hoiatab: signed/unsigned mismatch

Seega … on suht 50/50 kas kompilaator möliseb või ei. Ja tulemused on sellised (Windows XP all):

Borland 5.5.1:
Ebig = -16 E1 ? -1 = greater Ebig ? -1 = less
Digital Mars 8.38:
Ebig = 4294967280 E1 ? -1 = greater Ebig ? -1 = greater
Comeau 4.3.3 (EDG 3.3):
Ebig = 4294967280 E1 ? -1 = less Ebig ? -1 = less
gcc 2.95.3:
Ebig = 4294967280 E1 ? -1 = less Ebig ? -1 = less
gcc 3.3.2:
Ebig = 4294967280 E1 ? -1 = less Ebig ? -1 = less
Metrowerks Code-Warrior 8.3:
Ebig = -16 E1 ? -1 = greater Ebig ? -1 = less
Microsoft Visual C++ 6.0:
Ebig = -16 E1 ? -1 = greater Ebig ? -1 = less
Microsoft Visual C++ 7.1:
Ebig = 4294967280 E1 ? -1 = less Ebig ? -1 = less
Microsoft Visual C++ 8.0:
Ebig = -16 E1 ? -1 = greater Ebig ? -1 = less

Niisiis ei ole (vähem või rohkem) populaarsetel kompilaatoritel ka siin mingit üksmeelt. Ei nendel, kes hoiatasid kui ka nendel, kes midagi ei hoiatanud. ;-)

Njah, ja lõpetuseks … mõned soovitused:

  • Suhtu enumeraatorisse, kui pisut paremini konteksti seotud täisarv konstanti. Ära aga looda, et kompilaator sinu enumeraatorite kasutamise nüanssidel silma peal hoiab.
  • Suhtu C++ enumisse, kui mitteporditavasse täisarvu tüüpi. Väldi binaarsel kujul enumi kasutamise üritamist interfaces muude (isegi sama firma tehtud) kompilaatorite peal kompileeritud moodulitega või teises keeles kirjutatud moodulitega. Madalal tasemel interfaces kasuta POD-e http://en.wikipedia.org/wiki/Plain_Old_Data_Structures , mille sees enumeid ei ole, kõrgemad tasemed spekivad tavaliselt ise ära mis seal liigub (XML jne).
  • Suhtu enumisse, kui ebamääraste limiitidega täisarvu tüüpi. Väldi enumi kasutamist selliste muutujate deklareerimiseks, mis “int” sisse väga vabalt ära ei mahu.
  • Suhtu enumisse, kui lõdvalt konverteeruvasse tüüpi. Kui vaikides täisarvuks konverteerumine on konkreetse (strateegiliste tuumapeade olekut määrava) tüübi puhul liiga suur risk siis ära kasuta enumit vaid loo tüüp, mis täisarvuks (ega booliks, ega mõneks enumiks, ega isegi mitte void pointeriks) vaikimisi ei konverteeru.
  • Ja muidu … enjoy, enum on tihtilugu märksa elegantsem kui lihtsalt int.

5 thoughts on “C++ ja enum”

  1. http://blog.slickedit.com/?p=175 paistab suht OK jutt … võiks olla pigem meie
    wiki/c_/c_kirjutamise_reeglid alamhulk. Peakski viimase üle vaatama, nats enumite kohta täiendama, võib olla pointeritest rohkem rääkima.

    Aga sealtoodud soovitusega: “if (p) delete p;” ma küll nõusse ei jää. Esiteks ei meeldi mulle pointeri vaikimisi bool-iks teisendamine. Viisakas C++ näeb välja nii “if ( p != NULL )”. Teiseks, standard ütleb nii:

    “The value of the first argument supplied to one of the deallocation functions provided in the standard library may be a null pointer value; if so, the call to the deallocation function has no effect.”

    Seega … standardne delete, kui selline, eeldab ja kontrollib ise nulli oma definitsiooni kohaselt. “delete p; p = NULL;” lihtsalt ja ei miskeid kasutuid iffe.

    Destruktori sees aga on ka “p = NULL;” mõttetu. Nõrk. Kui nekrofiilitõke siis olgu see ka jõuline ja elegantne. Näiteks iga public memberfunktsiooni algusse “if (this == NULL || this_is_destroyed) exit(666);” Või kui on olemas, siis selline exit() ekvivalent, mis errorit logib. Jaa, “exit”, pood kinni laksust. Igasugu “throw”d tõelisele paranoikule ei piisa … neid ju nekrofiilid püüavad ja ignoreerivad. Igajuhul “p = NULL;” destruktoris on liiga hambutu. ;-)

  2. > Seega … standardne delete, kui selline, eeldab ja kontrollib ise nulli oma
    > definitsiooni kohaselt. “delete p; p = NULL;” lihtsalt ja ei miskeid kasutuid
    > iffe.

    pointeri nullimisest saadav turvatunne on petlik – pointer, mis deletele ette antakse ei pruugi olla sellest pointerist ainus koopia. lihtsalt “delete p;” ja kõik.

  3. “if (this == NULL || this_is_destroyed)” sisaldab undefined behavior’it:
    1) esiteks praeguse standardi kohaselt ei tohiks this null olla, sest et sellist olukorda tekitada, peaks dereferentsima NULL pointerit:

    Blah* p = NULL;
    p->f();
    või
    (*p).f();

    2) kui this on selline pointer, millele juba delete tehtud, siis this_is_destroyed (oletatavasti on see mingi klassi member ja mitte-static sealjuures) lugemine tähendab invalideeritud pointeri dereferentsimist.

  4. Loomulikult tasub nullida vaid neid pointereid mille nullimisest on mingit kasu. Ei viitsinud ju siia uuesti kirjutada et p all peeti seal artiklis silmas klassi member pointerit, mille taga oli miski klassi privaatne kompositsioonielement või agregaat.

    Ja UD-dest kubisev näide oli mul toodud selle kohta mida teha siis kui millegi pärast tuleb isu selliseid pointereid ka destruktoris nullima hakata. Minu väide oli, et siis pole nullimisest enam kasu.
    Oled tõenäoliselt olukorras/töökollektiivis kus tuleb tihtilugu ette et null pointer või invalideeritud pointer dereferentsitakse Sinu klassi pointeriks ära ja siis söödetakse Su klassi memberfunktsioonile ette. Jä pärast küsitakse et miks Su klass kräshib. Ja siis Sa lisad selle koodi oma klassile ja ütled, et enam ei kräshi. Ilusti paneb poe kinni nüüd. Või miks muidu sai Sul tulla tahtmist privaatset memberpointerit destruktoris nullida? ;)

Leave a Reply

Your email address will not be published. Required fields are marked *