C++ a výjimka v destruktoru

Re:C++ a výjimka v destruktoru
« Odpověď #60 kdy: 05. 02. 2014, 22:10:27 »

Takže pokud při tom vyletí výjimka, tak vím že jeden objekt nepřežil cestu tam a kdo-ví-kolik jich zařvalo při tom rollbacku. Takže vlastně o obsahu toho vektoru nevím vůbec nic. No hlavně že je to rychlé. ;D

A co tedy navrhuješ?


Re:C++ a výjimka v destruktoru
« Odpověď #61 kdy: 05. 02. 2014, 22:22:27 »
Nevzpomínám si, že bych v destruktoru někdy chrlil výjimky  :-\

Ani jsem netušil, že to jde.
Bible Kralická, přísloví 26

3 Bič na koně, uzda na osla, a kyj na hřbet blázna.
7 Jakož nejednostejní jsou hnátové kulhavého, tak řeč v ústech bláznů.
14 Dvéře se obracejí na stežejích svých, a lenoch na lůži svém.
27 Kdo jámu kopá, do ní upadá, a kdo valí kámen, na něj se obrací.

Krleš!

JSH

Re:C++ a výjimka v destruktoru
« Odpověď #62 kdy: 05. 02. 2014, 22:25:49 »
A co tedy navrhuješ?
No já navrhuju neházející destruktory samozřejmě. Pokud můžou destruktory házet tak prostě řešení neznám a bojím se, že ho nevymyslel ani nikdo z autorů normy C++.

Problém je už přenos jednoho prvku kombinací konstruktor-destruktor, pokud oba můžou selhat. Příklad :
kopie zdroj->cíl OK
destrukce zdroje - CHYBA - rollback
kopie cíl->zdroj - CHYBA
A co teď? S tímhle by si poradil leda destruktor, který při výjimce nic nelikviduje, ale ten zase nemusí zlikvidovat cíl, pokud by ta kopie zpátky prošla.

Re:C++ a výjimka v destruktoru
« Odpověď #63 kdy: 05. 02. 2014, 22:34:29 »
A co tedy navrhuješ?
No já navrhuju neházející destruktory samozřejmě. Pokud můžou destruktory házet tak prostě řešení neznám a bojím se, že ho nevymyslel ani nikdo z autorů normy C++.

Problém je už přenos jednoho prvku kombinací konstruktor-destruktor, pokud oba můžou selhat. Příklad :
kopie zdroj->cíl OK
destrukce zdroje - CHYBA - rollback
kopie cíl->zdroj - CHYBA
A co teď? S tímhle by si poradil leda destruktor, který při výjimce nic nelikviduje, ale ten zase nemusí zlikvidovat cíl, pokud by ta kopie zpátky prošla.
Jestli sis ráčil všimnout, celé je to postavené na tom, že operace moveObject se dá specializovat. Takže tam vůbec nemusí být konstrukce->destrukce. Ale to je jedno. Klidně si tam může specializovat vlastní operaci, která výjimku z destruktoru zaignoruje a pojede se vesele dál. Ale ten princip zodpovědnosti za konzistenci se tím naruší a to není možné dopustit. To že se potom může ztratit konzistence rollbackem už není takový problém, jako když se ztratí konzistence při normální operaci (a nezahlásí se to jako výjimka). Že se ti někde při roztahování pole poztrácela data, to ti asi nevadí. Zato ti vadí, když se postrácej při rollbacku. No mě zase vadí spíš to první a při rollbacku je mi to v celku jedno, protože to beztak možná skončí destrukcí celého pole až někam na základní úroveň.

A tos neviděl tu alchymii při vkládání prvků na pozici (posun všeho doprava), nebo při mazání jednoho až n prvků z prostřed. Tam si užiješ destruktorů... Já vím, že STL tohle neřeši. Je to jednoduché, stačí výjimky zakázat a tím je problém vyřešen. No není. Možná tak úředně.

JSH

Re:C++ a výjimka v destruktoru
« Odpověď #64 kdy: 05. 02. 2014, 22:45:20 »
Jestli sis ráčil všimnout, celé je to postavené na tom, že operace moveObject se dá specializovat. Takže tam vůbec nemusí být konstrukce->destrukce.
Vlastně ani nesmí, pokud to má fungovat vždycky.
Citace
Ale to je jedno. Klidně si tam může specializovat vlastní operaci, která výjimku z destruktoru zaignoruje a pojede se vesele dál.
fault tolerant != fault ignorant
Citace
Ale ten princip zodpovědnosti za konzistenci se tím naruší a to není možné dopustit. To že se potom může ztratit konzistence rollbackem už není takový problém, jako když se ztratí konzistence při normální operaci (a nezahlásí se to jako výjimka). Že se ti někde při roztahování pole poztrácela data, to ti asi nevadí. Zato ti vadí, když se postrácej při rollbacku. No mě zase vadí spíš to první a při rollbacku je mi to v celku jedno, protože to beztak možná skončí destrukcí celého pole až někam na základní úroveň.
A co takhle to při výjimce hnedka všechno zrušit? Stejně se nedá spolehnout na to, co zůstane po tom přenosu zpátky, tak proč se vůbec obtěžovat?
Citace
A tos neviděl tu alchymii při vkládání prvků na pozici (posun všeho doprava), nebo při mazání jednoho až n prvků z prostřed. Tam si užiješ destruktorů... Já vím, že STL tohle neřeši. Je to jednoduché, stačí výjimky zakázat a tím je problém vyřešen. No není. Možná tak úředně.
No právě protože vím jak těžké je zaručit exception-safety, tak jsem se na to ptal. Ono stačí zakázat výjimky v destruktorech a najednou jsou z neřešitelných problémů problémy obtížné. 8)


Re:C++ a výjimka v destruktoru
« Odpověď #65 kdy: 05. 02. 2014, 22:55:29 »
A co takhle to při výjimce hnedka všechno zrušit? Stejně se nedá spolehnout na to, co zůstane po tom přenosu zpátky, tak proč se vůbec obtěžovat?
To myslíš vážně, nebo si děláš srandu?

Citace
A tos neviděl tu alchymii při vkládání prvků na pozici (posun všeho doprava), nebo při mazání jednoho až n prvků z prostřed. Tam si užiješ destruktorů... Já vím, že STL tohle neřeši. Je to jednoduché, stačí výjimky zakázat a tím je problém vyřešen. No není. Možná tak úředně.
No právě protože vím jak těžké je zaručit exception-safety, tak jsem se na to ptal. Ono stačí zakázat výjimky v destruktorech a najednou jsou z neřešitelných problémů problémy obtížné. 8)
Ale jde to zaručit, jen to nikdo nechce řešit. Při mazání se výjimka destruktoru řeší tak, že se objekt považuje beztak za zničení, takže rollback se řeší pokračováním operace ničení (a následného přesunu ovšem už v režimu rollback jako stack unwind, takže každý destruktor ví, že už letí výjimka). Totéž pokud dojde k výjimce při konstrukci během vkládání. Opět se to rollbackuje (destruuje již vložené a přesouvá zpět) a to v rámci stack unwindingu.

A pokud by tě zajímalo, jak spustím operaci tak, aby se tvářila jako stack unwind, tak věř mi, je to jednodušší než si myslíš. Napíšu operaci jako třídu, tělo nechám vykonat v destruktoru. Pak jí zkonstruuj a ihned po její konstrukci udělám throw; Destuktor - potažmo vlastní rollback operace se provádí v rámci stack unwind.

Takhle třeba řeším výjimku v destruktoru při ničení celého pole. Destruuje je odzadu a když dojde výjimce, pokračuju v destrukci dopředu ovšem jako stack unwind. Naprosto totožné se totiž děje, když ti při destrukci objektu jeden z memberů vyhodí výjimku.

Sakra, všechno je v tom jazyce popsané. Celý systém je geniálně vymyšlený. Někdy mi přijde, že syntaxi a pravidla dělal jeden tým, zatímco ti méně schopní dostali na starost standardní knihovnu. Podle toho to tak vypadá. Jazyk má geniální logický pravidla, které ta knihovna ani neumí pořádně využít.

Sten

Re:C++ a výjimka v destruktoru
« Odpověď #66 kdy: 05. 02. 2014, 23:09:17 »
Jo, to je pravda. Pozapomněl jsem na to, že fopen kontroluje null jen v některých implementacích. Chtěl jsem napsat ilustrační příklad a nedomyslel jsem ho. Osobně používám unique_ptr, ale nechtělo se mi sem psát ten funktor.

Takže si napíšu funktor na destrukci. Pak si napíšu funktor pro čtení dat, potom ještě pro bufferovaný zápis. A pak zjistím, že jsem tím zmršil zapouzdření, takže mám najednou bordel v tom, kdo má za co zodpovědnost, jelikož je ta implementace různě poházená po programu a bůhví v kolika kopiích (soukromě tomu přezdívám neverending bugfixing nightmare). A pak to celé refaktoruju a přepíšu do streamů. Anebo to můžu udělat rovnou ;)

Škoda že nejspíš nikdy neuvidím zdrojáky té LightSpeed knihovny. Zajímalo by mě, jak je tam řešená realokace vektoru (nebo toho podobného), když můžou destruktory házet. Už když destruktory házet nemůžou to není úplně triviální věc.

U realokace vektoru to řešit nepotřebujete, protože destruktory se budou volat až po té realokaci, takže rollback není potřeba, pole již bylo přesunuto a je konzistentní (navíc by ty objekty měly použít move konstruktory, takže nemají co destruovat a tedy ani vyhazovat výjimky). Ale chápu pointu. Je to celkem snadné, když víte, jak na to:
Kód: [Vybrat]
void Array::clear()
{
struct Deleter
{
Array & arr;
int index;

Deleter(Array & arr)
: arr(arr)
, index(arr.size())
{}

~Deleter()
{
destroy();
delete arr.data;
}

void destroy()
{
while (index > 0)
arr.data[--index].~T();
}
} deleter;

deleter.destroy();
}

JSH

Re:C++ a výjimka v destruktoru
« Odpověď #67 kdy: 05. 02. 2014, 23:11:23 »
To myslíš vážně, nebo si děláš srandu?
Tohle myslím naprosto vážně. Pokud přesun jednoho prvku může selhat tak po nepovedené realokaci prostě netuším, jestli jsou ty původní prvky rollbackuté všechny nebo jich pár zařvalo. A takový výsledek se hodí k jedinému - hodit do stoupy a začít znova.

Citace
Ale jde to zaručit, jen to nikdo nechce řešit...
Jo, je spousta řešení, která skoro fungují. Ale nějak chybí řešení, která fungují. A odignorovat všechny problematické výjimky taky není řešení.

gamer

Re:C++ a výjimka v destruktoru
« Odpověď #68 kdy: 05. 02. 2014, 23:23:07 »
FYO reference counting u STL řetězců je pokud vím zakázaný, nebo aspoň silně nedoporučovaný.

Opravdu? Potom by asi bylo dobré autorům STL vysvětlit, že to dělají úplně špatně a nemají reference counting používat:
Kód: [Vybrat]
#include <string>
#include <iostream>

int main()
{
    std::string s1 = "a";
    std::string s2 = s1;
    std::cout << reinterpret_cast<const void*>(s1.c_str()) << std::endl;
    std::cout << reinterpret_cast<const void*>(s2.c_str()) << std::endl;
    s2[0] = 'b';
    std::cout << reinterpret_cast<const void*>(s1.c_str()) << std::endl;
    std::cout << reinterpret_cast<const void*>(s2.c_str()) << std::endl;
    return 0;
}
g++ -O2 main.cc ; ./a.out
0xa35028
0xa35028
0xa35028
0xa35058

JSH

Re:C++ a výjimka v destruktoru
« Odpověď #69 kdy: 05. 02. 2014, 23:29:06 »
Takže si napíšu funktor na destrukci. Pak si napíšu funktor pro čtení dat, potom ještě pro bufferovaný zápis. A pak zjistím, že jsem tím zmršil zapouzdření, takže mám najednou bordel v tom, kdo má za co zodpovědnost, jelikož je ta implementace různě poházená po programu a bůhví v kolika kopiích (soukromě tomu přezdívám neverending bugfixing nightmare). A pak to celé refaktoruju a přepíšu do streamů. Anebo to můžu udělat rovnou ;)
Takže si napíšu funktor na destrukci a ... a tím to končí. Jaké další funktory jsou potřeba? Na otestování návratové hodnoty dalších funkcí a případné vyhození výjimky není třeba žádný funktor. A taky není třeba balit každý fwrite zvlášť.
Citace
U realokace vektoru to řešit nepotřebujete, protože destruktory se budou volat až po té realokaci, takže rollback není potřeba, pole již bylo přesunuto a je konzistentní (navíc by ty objekty měly použít move konstruktory, takže nemají co destruovat a tedy ani vyhazovat výjimky). Ale chápu pointu. Je to celkem snadné, když víte, jak na to:
Kód: [Vybrat]
void Array::clear()
{
struct Deleter
{
Array & arr;
int index;

Deleter(Array & arr)
: arr(arr)
, index(arr.size())
{}

~Deleter()
{
destroy();
delete arr.data;
}

void destroy()
{
while (index > 0)
arr.data[--index].~T();
}
} deleter;

deleter.destroy();
}
Nevím, jakou relevanci má tenhle kód k mému dotazu. Nic se tam nerealokuje, jen likviduje. Samozřejmě, že umím zrušit všechny prvky vektoru a odignorovat chyby.
Co neumím je vektor přealokovat, pokud může házet jak kopírovací konstruktor, tak destruktor. Jo, umím to přesunout jako by ty destruktory neházely a odignorovat, co vyhodí. Jen tu výhodu proti noexcept destruktorům v tom nejak stále nevidím. No ale aspoň nepřijdu o žádný prvek, pokud vyletí výjimka. To je proti kódu Ondřeje Nováka jednoznačné plus.

Sten

Re:C++ a výjimka v destruktoru
« Odpověď #70 kdy: 06. 02. 2014, 00:12:56 »
Takže si napíšu funktor na destrukci a ... a tím to končí. Jaké další funktory jsou potřeba? Na otestování návratové hodnoty dalších funkcí a případné vyhození výjimky není třeba žádný funktor. A taky není třeba balit každý fwrite zvlášť.

Například pro načtení jednoho řádku do stringu nebo i pro zápis stringu, když nechcete pořád psát fwrite(str.c_str(), str.size(), 1, *file).

Nevím, jakou relevanci má tenhle kód k mému dotazu. Nic se tam nerealokuje, jen likviduje. Samozřejmě, že umím zrušit všechny prvky vektoru a odignorovat chyby.

Já ty chyby ale neignoruji ;)

Co neumím je vektor přealokovat, pokud může házet jak kopírovací konstruktor, tak destruktor. Jo, umím to přesunout jako by ty destruktory neházely a odignorovat, co vyhodí. Jen tu výhodu proti noexcept destruktorům v tom nejak stále nevidím. No ale aspoň nepřijdu o žádný prvek, pokud vyletí výjimka. To je proti kódu Ondřeje Nováka jednoznačné plus.

  • Při realokaci většinou používáte move konstruktory, ne kopírovací konstruktory. Ty bývají nothrow (prostě jenom seberou zdroje), a protože objektu všechny zdroje seberou, tak následná destrukce žádné zdroje neuvolňuje a tedy ani nevyhazuje výjimky.
  • Je špatný návrh destruovat každý objekt hned poté, co jej přesunete (zkopírujete). Daleko lepší je přesunout (zkopírovat) všechny objekty a až poté destruovat, dost si usnadníte zachovávání konzistence při výjimkách. Navíc vyhazovat mohou nejen destruktory, ale i konstruktory, takže výjimky v tomto případě musíte řešit stejně.

Re:C++ a výjimka v destruktoru
« Odpověď #71 kdy: 06. 02. 2014, 01:47:37 »
    • Je špatný návrh destruovat každý objekt hned poté, co jej přesunete (zkopírujete). Daleko lepší je přesunout (zkopírovat) všechny objekty a až poté destruovat, dost si usnadníte zachovávání konzistence při výjimkách. Navíc vyhazovat mohou nejen destruktory, ale i konstruktory, takže výjimky v tomto případě musíte řešit stejně.

    Tohle se obtížně řeší, když mám na přesun šablonovou funkci, takže nemohu vědět, co se děje uvnitř. To už asi nebude jednoduchá funkce, ale specializovaný objekt s dvěma metodama. Jedna pro přesun a druhá pro následnou destrukci. Já to řešil jednodušší cestou, protože mi přišlo zbytečně složité si komplikovat situaci, která nastane jen někdy (zpravidla nenastane). Ať už výjimka vypadne při kopírování nebo při destrukci, řeší se to rollbackem.

    Re:C++ a výjimka v destruktoru
    « Odpověď #72 kdy: 06. 02. 2014, 01:52:26 »

    Opravdu? Potom by asi bylo dobré autorům STL vysvětlit, že to dělají úplně špatně a nemají reference counting používat:


    Stav je takový, že tvůrci normy C++11 se docela lekly problémů, která by náhlá změna způsobila, že nakonec uvažovaný koncept do normy nezanesly. Nicméně stále by mělo platit, že jednoho dne bude prohlášeno, že reference counting u std::string nesmí být.

    Já to mám jednodušší, já si udělal vlastní stringy a u nich jsem řekl, že reference counting je u nich naopak povinný. Vyhnul jsem se tak jakési "právní" nejistotě okolo std::string  (sorry, ale nemohu stavět program na něčem, o čemž předem vím, že se možná v budoucnu výrazně změní)

    Sten

    Re:C++ a výjimka v destruktoru
    « Odpověď #73 kdy: 06. 02. 2014, 02:12:18 »
    Tohle se obtížně řeší, když mám na přesun šablonovou funkci, takže nemohu vědět, co se děje uvnitř. To už asi nebude jednoduchá funkce, ale specializovaný objekt s dvěma metodama. Jedna pro přesun a druhá pro následnou destrukci. Já to řešil jednodušší cestou, protože mi přišlo zbytečně složité si komplikovat situaci, která nastane jen někdy (zpravidla nenastane). Ať už výjimka vypadne při kopírování nebo při destrukci, řeší se to rollbackem.

    Selhat ale může i rollback. Pokud nejdříve všechno zkopíruji a až poté všechno zdestruuji, tak mám jistotu, že budu mít vždy konzistentní stav: buď ten původní (pokud selže kopírování) nebo ten nový (pokud selže destruování). Sice je to málo pravděpodobné, ale má to lepší exception safety pro případ, když už to nastane (strong místo basic).

    JSH

    Re:C++ a výjimka v destruktoru
    « Odpověď #74 kdy: 06. 02. 2014, 08:38:48 »
    Stav je takový, že tvůrci normy C++11 se docela lekly problémů, která by náhlá změna způsobila, že nakonec uvažovaný koncept do normy nezanesly. Nicméně stále by mělo platit, že jednoho dne bude prohlášeno, že reference counting u std::string nesmí být.
    Reference counting je povolený, ale navenek nesmí být vidět. To je hodně těžké udělat. Navíc s přibývajícími jádry není reference counting žádná výhra a pro krátké stringy je daleko rychlejší když je to krátké pole přímo uvnitř stringu.
    Citace
    Já to mám jednodušší, já si udělal vlastní stringy a u nich jsem řekl, že reference counting je u nich naopak povinný. Vyhnul jsem se tak jakési "právní" nejistotě okolo std::string  (sorry, ale nemohu stavět program na něčem, o čemž předem vím, že se možná v budoucnu výrazně změní)
    Koukám, že si z STL celkem konzistentně vybíráš věci, které se úplně nepovedly a ty povedené naopak ne.