Problémy jsou vždy. A tady je něco málo + řešení. Pokud máš nějaký zajímavý problém, tak se ozvi. Pokud bude zajímavý, rád ho zveřejním .. kde ? no přece tady.
Při vzniku a odchycení vyjímky by správně měli následovat tyto akce:
Konstruktor vyjímky
Po vstupu do bloku "catch" - Kopírovací konstruktor vyjímky (s tímto objektem budeme pracovat)
Destruktor vlastní vyjímky
Po ukončení bloku "catch" - destruktor kopie vyjímky
Ale pokud si ztáhneš tento soubor, vytvoříš si konzolový projekt se zaškrlou volbou "Include the Visual Component Library (VCL)" a vložíš do něj kód ze souboru, tak po spustění by jsi měl dostat výsledek:
Call exception CONSTRUCTOR of ORGINAL CONSTRUCTOR of COPY DESTRUCTOR of ORGINAL DESTRUCTOR of COPY
Ale po kompilaci v C-Builderu verze 3 se zaplou podporou VCL dostaneš výsledek:
Call exception CONSTRUCTOR of ORGINAL CONSTRUCTOR of COPY DESTRUCTOR of ORGINAL
Jak je vidět jedno volání destruktoru chybí (Bod 4 se neprovedl). A kdybys ještě odznačil volbu "Projekt->Options->C++" "Exception Handling" "Destructor Cleanup", tak se ti nezavola ani jeden destruktor.
A kdybys použil zápis "catch(...)" pro odchycení všech vyjímek, jak se často ikdyž ne moc chytře, ale pohodlně používá, tak nemáš ani jak tuto paměť uvolnit.
Závěr:
Pokud používáš C-Builder verzi 3 musíš si vyjímku uvolnit sám (na konci bloku "catch"). Pokud chceš kód přenášet doporučuji toto uvolnění dát do bloku podmíněčného překladu. POZOR toto uvolnění nesmí být použito v C-Builder verze 4.
Ve zdrojovém souboru je tato konstrukce použitá.
Pokud si chceš stáhnout malý test tak zde je zkomprimovany exe soubor a zde je zkomprimovaný projekt v C-Builderu verze 3. V testu je vyvolávána vyjímka a odchytávána. Pokud zaškrkneš volbu pro volaní delete (toto volání je vloženo na konci "catch" bloku), tak bude vše OK, ale pokud ne, tak ti pomalu ale jistě bude ubývat paměť.
Pokud si z třídy Exceptions (nebo z jiné odvozené z této třídy), která je definovaná v pascallu, odvodíš jinou vyjímkovou třídu a po té tuto vyjímku zavoláš a odchytíš, TAK:
Se zavolá konstruktor vyjímky
Po ukončení bloku "catch" se vyjimka uvolní (v C-Builderu verze 3 ne)
Správně (dle normy C++) by se při odchycení měla vytvořit kopie vyjímkového objektu pomocí kopírovacího konstruktoru. S tímto zkopírovaným objektem potom pracujeme v bloku "catch".
Ale pokud si ztáhneš tento soubor, vytvoříš si konzolový projekt se zaškrlou volbou "Include the Visual Component Library (VCL)" a vložíš do něj kód ze souboru, tak po spustění by jsi měl dle pravidel C++ dostat výsledek:
Call exception CONSTRUCTOR of ORGINAL CONSTRUCTOR of COPY DESTRUCTOR of ORGINAL DESTRUCTOR of COPY
Ale po kompilaci dostaneš výsledek:
Call exception CONSTRUCTOR of ORGINAL DESTRUCTOR of ORGINAL
Závěr:
Nevolá se kopírovací konstruktor, proto pokud děláš něco jiného v konstruktoru a v kopírovacím konstruktoru, tak máš smůlu, protože kopírovací konstruktor se nikdy nezavolá. S uvolněním vyjímky pod C-Builderem verze 3 platí také problém předchozí (vyjímka se neuvolní, musíš sám).
U operatoru "new", sloužící pro alokaci "hrubé paměti" nebo allokaci pamětí pro objekty je jeden zásadní problém a to jak ošetřit uspěšnou alokaci. Teď si asi říkáš co je na tom - vysvětlím.
Pokud víš jaký překladač používáš a jak funguje a víš jak jsou psány všechny zdrojové soubory , tak není problém. Problém nastane až když chceš kód někam přenést nebo přejdeš třeba z C-Builderu verze 4 na verzi 5.
Záludnost je v tom, že co překladač to jiný způsob "oznámení" o neúspěchu alokace paměti. Dle normy ANSI/ISO by se měla při neúspěšné alokaci vyvolat vyjímka "bad_alloc". Pokud si byl zvyklý na nějaký staší překladač (např. BC ver.3), tak máš smůlu protože tam se vyjímka nevyvolala ale ukazatel byl nastaven na 0 (NULL). Je tady možnost použití funkce "set_new_handler", která umožňuje přestavení chování "new" při neúspěšné alokaci. A jako vždy je tady ALE - set_new_handler(0) by dle normy měl vrátit(nastavit) vyvolání vyjímky "bad_alloc", ale dokonce i v helpu od C-Builderu verze 4 se dočtete něco jiného. Přeložil jsem stejný kód, který se pokuší alokovat 2Gb paměti (doufám že tolik nemáš) na různých překladačích a tady jsou výsledky:
C-Builder verze 3 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven na 0 (NULL)... dle normy (tady se mně nepodařilo alokovat pole int, ale já tento nástroj nepoužívám, tak jsem se s tím netrápil) - "new" vyvolá vyjímku "bad_alloc"... dle normy - set_new_handler(0) nastaví chování "new" bez vyjímky a nastavení ukazatele na 0 (NULL)... nesouhlasí z normou
C-Builder verze 4 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven na 0 (NULL)... dle normy - "new" vyvolá vyjímku "bad_alloc"... dle normy - set_new_handler(0) nastaví chování "new" bez vyjímky a nastavení ukazatele na 0 (NULL)... nesouhlasí z normou
Borland C++ Compiler 5.5 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven an 0 (NULL)... dle normy - "new" vyvolá vyjímku "bad_alloc"... dle normy - set_new_handler(0) nastaví chování "new" na vyvolání vyjímky "bad_alloc"... dle normy
Microsoft Visual Studio verze 6.0 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven an 0 (NULL) - "new" nevyvolá vyjímku a ukazatel je nastaven an 0 (NULL)... nesouhlasí z normou - set_new_handler(0) nastaví chování "new" na nevyvolání vyjímky a mám podezření, že ve skutečnosti nedělá vůbec nic... nesouhlasí z normou
Linux GCC - RedHat verze 6.0 - Tady to dopadlo bídňe. Nebyla volána vyjímka a ani nebyl ukazatel nastaven na "0" - nikdy. Jen se vytvořilo "core" (Zkoušel jsem to na dvou různých instalacích)
Linux GCC - RedHat verze 6.2 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven an 0 (NULL)... dle normy - "new" vyvolá vyjímku "bad_alloc"... dle normy - set_new_handler(0) nastaví chování "new" na vyvolání vyjímky "bad_alloc"... dle normy
UNIX (SGI Origin) Compilator ver 7.3 - podporuje "new(nothrow)" nevyvolána vyjímku a ukazatel je nastaven an 0 (NULL)... dle normy - "new" vyvolá vyjímku "bad_alloc"... dle normy - set_new_handler(0) nastaví chování "new" na vyvolání vyjímky "bad_alloc"... dle normy
Závěr:
Dle normy se z "windowsových" překladačů chová jen "Borland C++ Compiler 5.5".
Máš asi co upravovat pokud využíváš nějaké kódy napsané dříve, či chceš mít nějaký kód přenositelný. Samozřejmě máš možnost použít operátor "new(nothrow)" a ošetřovat alokaci paměti na návrat 0 (NULL) - to by mělo platit vždy.
//možná budeš potřebovat tento řádek (např. C-Builder ver.4) const std::nothrow_t std::nothrow;
Ale stejně tak se musíš přesvědčit jestli to tvůj překladač podporuje.
Já si myslím, že každá dynamická alokace paměti byť sebemenší, by měla být ošetřena. Ale jak to zařídit, aby bylo možné programu přenášet z překladače na překladač to nevím. Doufejme, že pokud se budem držet normy, tak nás tvůrci překladačů (i od microsoftů) mile překvapí a budou se jí držet taky. Ale buďme klidní, oni určitě zase přijdou z jiným rozporem.