[C++] Exception handling

Pagina: 1
Acties:

  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Kan iemand mij helpen met het volgende probleem; zie dit stukje code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Dinges
{
  Dinges();
  ~Dinges();
  EenDing* Iets; // EenDing en AnderDing zijn in een andere file gedeclareerd en zijn verder niet relevant
  AnderDing* NogWat;
}

Dinges::Dinges()
{
  Iets = new EenDing;
  if(!Iets)
    throw GroteFout;
  NogWat = new AnderDing;
  if(!NogWat)
    throw GroteFout;
}

Dinges::~Dinges()
{
  if(NogWat)
    delete NogWat;
  NogWat = NULL;
  if(Iets)
    delete Iets;
  Iets = NULL;
}

void main()
{
  Dinges* Ding;
  try
  {
    Ding = new Dinges;
  }
  catch(GroteFout)
  {
    MessageBox(NULL, "Hellep! Grote fout! Programma sluit af!", "Oh my god!", MB_OK);

    // MAAR: Als 'new NogWat' deze fout heeft veroorzaakt
    // wordt Iets niet gedelete omdat de destructor niet wordt aangeroepen.
    // En deze kun je niet met de hand aanroepen omdat Ding dan een NULL-pointer is.

    return;
  }
  delete Ding;
}

Hoe kijg ik nu voor elkaar dat als NogWat niet kan worden gecreëerd, dat dan toch de destructor ~Dinges wordt aangeroepen om Iets op te ruimen?

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • ^Mo^
  • Registratie: Januari 2001
  • Laatst online: 04-11-2025
lokaal in de constructor opvangen en eventueel opruimen... Daarna moet je nog wel weer doorgooien (met throw)... Je zet trouwens die pointers nog op NULL in de destructor, dat is eigenlijk overbodig.. (maar da's een detail ;))

- Edit -
Oh ja, je kan nu geen dinges object aanmaken aangezien je constructor private is (je destructor trouwens ook)

"There are 10 kinds of people in the world, those who understand binary and those who don't" | Werkbak specs


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Op zondag 28 april 2002 19:34 schreef _Mo_ het volgende:
lokaal in de constructor opvangen en eventueel opruimen... Daarna moet je nog wel weer doorgooien (met throw)...
zucht... exception handling is zo mooi, moet je weer C-stijl gaan programmeren :( Maar thnx anyway.
Je zet trouwens die pointers nog op NULL in de destructor, dat is eigenlijk overbodig.. (maar da's een detail ;))
Mwah, goeie gewoontes overboord gooien is ook lastig... ik heb nog niet zo veel ervaring met dit soort dingen.
- Edit -
Oh ja, je kan nu geen dinges object aanmaken aangezien je constructor private is (je destructor trouwens ook)
Ehm ja, maar ik schrijf dus normaal ook geen programma's met objecten die Dinges heten... behalve als ik Notepad gebruik... maar het gaat om het idee he ;)

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
En als ik nou 10, 20 of weet ik veel hoeveel objecten creeer in de constructor, hoe wordt dit dan? Moet ik bij elke throw alles deleten wat daarvoor is gemaakt??? (Of ben ik gewoon weer dom...)
edit:
catch in throw veranderd |:(

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • ^Mo^
  • Registratie: Januari 2001
  • Laatst online: 04-11-2025
Op zondag 28 april 2002 19:42 schreef WildernessChild het volgende:

[..]

zucht... exception handling is zo mooi, moet je weer C-stijl gaan programmeren :( Maar thnx anyway.
[..]

Mwah, goeie gewoontes overboord gooien is ook lastig... ik heb nog niet zo veel ervaring met dit soort dingen.
[..]

Ehm ja, maar ik schrijf dus normaal ook geen programma's met objecten die Dinges heten... behalve als ik Notepad gebruik... maar het gaat om het idee he ;)
Ik zie eigenlijk geen andere manier dan lokaal opvangen. Of je kan het ook anders doen, dan zet je in de constructor van Dinges de twee pointers op NULL, maak je twee functies die deze pointers initialiseren (en dus ook kunnen gooien) (of juist een functie die alle pointers initialiseren) en dan roep je in je catch block in main de destructor van Dinges aan, laat die dan maar uitvissen wat er wel en niet bestaat. Zorg trouwens wel dat je niet deze nieuwe functie in je constructor gaat aanroepen...

"There are 10 kinds of people in the world, those who understand binary and those who don't" | Werkbak specs


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Ik kan deze destructor juist niet vanuit main aanroepen; als er gegooid wordt in de constructor retourneert new een NULL pointer en kan ik niet bij m'n object...

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • ^Mo^
  • Registratie: Januari 2001
  • Laatst online: 04-11-2025
nee, maar dan wordt het zo:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Dinges
{
    public:
      /* Al het voorgaande */
      void InitPointers();
};

Dinges::Dinges()
    : Iets( NULL), NogWat( NULL)
{
}

/* Destructor code hier */

void Dinges::InitPointers()
{
    Iets = new EenDing;
    if( !Iets) throw GroteFout;
    NogWat = new AnderDing;
    if( !NogWat) throw GroteFout;
}

/* In main */
void main()
{
    Dinges *Ding = NULL;
    try
    {
      Ding = new Dinges;
      if( Ding) Ding->InitPointers();
    }
    catch( GroteFout)
    {
      /* Je messagebox en eigenlijk hoeft het opruimen al
         niet meer gezien het feit dat de volgende call al
         de destructor is, mocht dat niet het geval zijn
         roep dan hier nog de destructor aan en return
       */
    }
    if( Ding) delete Ding;
}

- Edit -
Members public access gegeven ;)

"There are 10 kinds of people in the world, those who understand binary and those who don't" | Werkbak specs


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:35

.oisyn

Moderator Devschuur®

Demotivational Speaker

je kan natuurlijk ook gewoon je Dinges meegeven met GroteFout

dus in de constructor van Dinges: throw GroteFout (this);
(en dan moet je GroteFout natuurlijk zo aanpassen dat ie Dinges als parameter accepteert (of een template maken voor een wat generiekere oplossing))

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • ^Mo^
  • Registratie: Januari 2001
  • Laatst online: 04-11-2025
Op maandag 29 april 2002 03:31 schreef .oisyn het volgende:
je kan natuurlijk ook gewoon je Dinges meegeven met GroteFout

dus in de constructor van Dinges: throw GroteFout (this);
(en dan moet je GroteFout natuurlijk zo aanpassen dat ie Dinges als parameter accepteert (of een template maken voor een wat generiekere oplossing))
Maar in het geval van dit programma zou je in principe alles kunnen gooien, als ie maar gooit. Je kunt inderdaad de this pointer meegeven en daarmee opruimen (heb je geneens een template voor nodig, void pointer is al genoeg).

"There are 10 kinds of people in the world, those who understand binary and those who don't" | Werkbak specs


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Auw.

Je programma heeft inderdaad een exception-probleem, en dat komt doordat je twee resources probeert te managen in een object. Dat gaat niet goed, precies om de redenen die je zelf opnoemde.

De oplossing is simpel: 1 resource = 1 manager object, en als je twee resources in een object nodig hebt dan moet die dus twee members hebben die het managen voor je doen. Bij een exception worden namelijk wel de dtors van alle members aangeroepen.

Voor jouw situatie is std::auto_ptr<> waarschijnlijk toereikend. Vergeet niet om zelf een copy constructor te schrijven, en een operator=.

Je neemt overigens ten onrechte aan dat new NULL teruggeeft. Niet dus, die gooit std::bad_alloc. Bovendien is een niet-geinitialiseerde pointer lang niet alijd NULL.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:35

.oisyn

Moderator Devschuur®

Demotivational Speaker

Op maandag 29 april 2002 05:37 schreef _Mo_ het volgende:

[..]

Maar in het geval van dit programma zou je in principe alles kunnen gooien, als ie maar gooit. Je kunt inderdaad de this pointer meegeven en daarmee opruimen (heb je geneens een template voor nodig, void pointer is al genoeg).
met een void pointer weet je niet wat het is dat fout is gegaan. Neem dit voorbeeld:
code:
1
2
3
4
5
6
7
8
9
try
{
    new Dinges;
    new Blaater;
}
catch (GroteFout g)
{
    // wat is nu verkeerd gegaan?
}

tov dit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
try
{
    new Dinges;
    new Blaater;
}
catch (GroteFout<Dinges> gd)
{
    // ruim dinges op
}
catch (GroteFout<Blaater> gb)
{
    // ruim blaater op
}

overigens ben ik het met MSalters eens (wow ook voor het eerst ;)) dat het een verkeerde manier van coden is

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

in je hoofd programma's try block:

std::auto_ptr<Dinges> ptr(new Dinges);

Done. :) (en #include <memory>) Als je constructor gooit, gaat de auto_ptr uit scope en wordt ie, omdat het een stack object is, destroyed, die dan op zn beurt jouw dinges ook destroyed via de destructor. Je destructor geeft gewoon alle resources vrij (en kijkt eerst of ze wel gealloceerd zijn)


(opmerking nog, ik zie niet in waarom je een copy constructor en assignment voor auto_ptr zou toevoegen, tenzij je *heel* goed weet wat je doet, klein foutje, niet thread safe reference count oid, en dan heb je alsnog een resource leak)

(opmerking 2, als je alleen maar geheugen alloceert in dat Dinges, en die fatal exception zorgt altijd dat je programma stopt, dan is dat in principe *geen* memory leak. Dit aangezien je process geheugen wordt vrijgegeven als je programma stopt. Niet echt netjes alleen...maarja, en om nou op windows memory mangagement te vetrouwen hmmm, gebruik toch maar die auto_ptr hehe)

  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op maandag 29 april 2002 15:25 schreef Zoijar het volgende:
in je hoofd programma's try block:

std::auto_ptr<Dinges> ptr(new Dinges);

Done. :) (en #include <memory>) Als je constructor gooit, gaat de auto_ptr uit scope en wordt ie, omdat het een stack object is, destroyed, die dan op zn beurt jouw dinges ook destroyed via de destructor. Je destructor geeft gewoon alle resources vrij (en kijkt eerst of ze wel gealloceerd zijn)
Fout. Als je Dinges ctor throwt, dan is de auto_ptr ctor nog niet aangeroepen (logisch), dus dan wordt de auto_ptr dtor niet aangeroepen, dus dan wordt jouw dtor ook niet aangeroepen.
(opmerking nog, ik zie niet in waarom je een copy constructor en assignment voor auto_ptr zou toevoegen, tenzij je *heel* goed weet wat je doet, klein foutje, niet thread safe reference count oid, en dan heb je alsnog een resource leak)
std::auto_ptr werkt blijkbaar niet zoals je denkt; al je een auto_ptr kopieert dan wijst het origineel niet meer naar het ene object. Daarnaast kun je geen members aan bestaande (std) classes toevoegen. Mijn originele punt in deze thread was dat classes die auto_ptr gebruiken daarom een eigen copy ctor moeten maken, die een nieuwe auto_ptr aanmaakt ipv de oude te kopieren.
(opmerking 2, als je alleen maar geheugen alloceert in dat Dinges, en die fatal exception zorgt altijd dat je programma stopt, dan is dat in principe *geen* memory leak. Dit aangezien je process geheugen wordt vrijgegeven als je programma stopt. Niet echt netjes alleen...maarja, en om nou op windows memory mangagement te vetrouwen hmmm, gebruik toch maar die auto_ptr hehe)
Memory leaks zijn niet het probleem. Andere dingen kunnen ook leaken (IP connecties en zo), en die ruimt Windows lang niet altijd netjes op. auto_ptr<T> kan die wel opruimen, of beter gezegd delegeren aan T::~T

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op zondag 28 april 2002 19:57 schreef _Mo_ het volgende:
nee, maar dan wordt het zo:
[code]class Dinges
{
public:
/* Al het voorgaande */
void InitPointers();
};
Ai. Zombies. Niet doen.

In deze opzet heb je zombie Dinges'en, als InitPointer nog niet aangeroepen is of gefaald heeft. Alle members moeten dan checken of de pointers NULL zijn. Dat is geen goed ontwerp. Je gaat toch ook zelf niet checken of je dood bent voordat je iets doet?

De originele opzet was goed, maar had het "twee domme resources" probleem. De oplossing is dan meer IQ, niet minder :)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • ^Mo^
  • Registratie: Januari 2001
  • Laatst online: 04-11-2025
Op maandag 29 april 2002 17:23 schreef MSalters het volgende:

[..]

Ai. Zombies. Niet doen.

In deze opzet heb je zombie Dinges'en, als InitPointer nog niet aangeroepen is of gefaald heeft. Alle members moeten dan checken of de pointers NULL zijn. Dat is geen goed ontwerp. Je gaat toch ook zelf niet checken of je dood bent voordat je iets doet?

De originele opzet was goed, maar had het "twee domme resources" probleem. De oplossing is dan meer IQ, niet minder :)
Ik vind het zowiezo een goede gewoonte om je pointers ALTIJD te testen... zelfs na elke new zou je in principe moeten testen, alhoewel dat in de praktijk wel zal meevallen.
Je neemt overigens ten onrechte aan dat new NULL teruggeeft. Niet dus, die gooit std::bad_alloc
Dat verschilt heel erg per compiler, bijvoorbeeld de Visual C compiler geeft NULL terug, ik erger me er nogal aan dat de ene compiler een exception laat gooien en de andere niet...

"There are 10 kinds of people in the world, those who understand binary and those who don't" | Werkbak specs


Verwijderd

Op maandag 29 april 2002 17:23 schreef MSalters het volgende:

[..]

Ai. Zombies. Niet doen.

In deze opzet heb je zombie Dinges'en, als InitPointer nog niet aangeroepen is of gefaald heeft. Alle members moeten dan checken of de pointers NULL zijn. Dat is geen goed ontwerp. Je gaat toch ook zelf niet checken of je dood bent voordat je iets doet?
Ach, C++ dwingt je sowieso eigenlijk al om een speciale Init methode te maken, aangezien bij het aanroepen van methoden vanuit de constructor herdefinitie in subklassen niet werkt, dus:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
public:
  A() { CreateC(); };
  virtual ~A();
  
  virtual C* CreateC();
}

class B : public A
{
public:
  B();
  virtual ~B();
  
  virtual C* CreateC();
}

Als je nu dus new B() doet dan wordt dus niet B::CreateC() uitgevoerd, maar A::CreateC() (in elke method ongelijk aan constructor gaat het wel goed), erg vervelend dus, hierdoor kun je vaak maar beter niet te veel bijzondere dingen doen in de constructor, daarmee voorkom je dus ook gelijk dat er exceptions worden gethrowd in de constructor wat dus zoals uit eerdere reacties is gebleken een beetje vervelend is.

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 04-09 14:38

curry684

left part of the evil twins

Op zondag 28 april 2002 19:42 schreef WildernessChild het volgende:
zucht... exception handling is zo mooi, moet je weer C-stijl gaan programmeren :( Maar thnx anyway.
Als je smart pointers gebruikt heb je dit probleem niet daar die bij de constructor-stack-unwind automatisch opgeruimd worden, en eventueel al toegewezen pointers worden dan wel automagisch mede opgeruimd.

Niet klagen dat je C-style moet als je nog niet de hele C++ trukendoos hebt opengetrokken he :Y)

Professionele website nodig?


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op dinsdag 30 april 2002 15:57 schreef hondass50 het volgende:

[..]

Ach, C++ dwingt je sowieso eigenlijk al om een speciale Init methode te maken, aangezien bij het aanroepen van methoden vanuit de constructor herdefinitie in subklassen niet werkt [SNIP code] Als je nu dus new B() doet dan wordt dus niet B::CreateC() uitgevoerd, maar A::CreateC() (in elke method ongelijk aan constructor gaat het wel goed), erg vervelend dus, hierdoor kun je vaak maar beter niet te veel bijzondere dingen doen in de constructor, daarmee voorkom je dus ook gelijk dat er exceptions worden gethrowd in de constructor wat dus zoals uit eerdere reacties is gebleken een beetje vervelend is.
Och - wat jij fout noemt gebeurt ook in de dtor, en met opzet. In de ctor van A heb je een A object. Een B object heb je pas als de B ctor runt. Als je van een A object de virtual functie CreateC aanroept dan krijg je A::CreateC.
Is logisch, toch?
Evengoed heb je in A::~A geen B object meer. Als je daar B::CreateC() zou aanroepen, en die gebruikt this->member_of_b, dan gaat dat waarschijnlijk fout.

Wat exceptions uit ctors betreft blijkt dus dat het ontzettend handig is; het is nl. de enige practische mogelijkheid om grotere objecten aan te maken met members en bases die zelf weer members en bases hebben, die allemaal initialisatie nodig hebben. De enige voorwaarde is dat elke resource een eigen dtor heeft. Simpele regel toch? En dan heeft de belangrijkste resource, geheugen, standaard dat soort dtors (string/vector/auto_ptr).

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Nog een vraagje. In de c'tor van de base class wil ik in een log een berichtje opslaan "class %s gemaakt". %s moet dan worden vervangen door de classname. Niet die van de baseclass, maar van de eigenlijke class die gemaakt wordt. Maar als ik schrijf LogMessage(typeid(*this).name()) dan logt-ie de naam van de base class. this heeft dus blijkbaar in deze c'tor nog het type van de base class. Hoe los ik dit op zonder de message in elke c'tor vn afgeleide classes opnieuw te loggen?

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 04-09 14:38

curry684

left part of the evil twins

Op donderdag 09 mei 2002 21:27 schreef WildernessChild het volgende:
Nog een vraagje. In de c'tor van de base class wil ik in een log een berichtje opslaan "class %s gemaakt". %s moet dan worden vervangen door de classname. Niet die van de baseclass, maar van de eigenlijke class die gemaakt wordt. Maar als ik schrijf LogMessage(typeid(*this).name()) dan logt-ie de naam van de base class. this heeft dus blijkbaar in deze c'tor nog het type van de base class. Hoe los ik dit op zonder de message in elke c'tor vn afgeleide classes opnieuw te loggen?
Check eens naar de predefined preprocessor constante __FUNCNAME__. Deze kun je wel loggen als ie bestaat in je compiler.

Professionele website nodig?


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op vrijdag 10 mei 2002 18:56 schreef curry684 het volgende:

[..]

Check eens naar de predefined preprocessor constante __FUNCNAME__. Deze kun je wel loggen als ie bestaat in je compiler.
Helpt dat dan?
De vraag is om een helderziende compiler. Dwz in Base::Base() moet er voorspeld worden of er een Derived object gemaakt gaat worden - wat dus met exceptions niet zeker is.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 04-09 14:38

curry684

left part of the evil twins

Op vrijdag 10 mei 2002 22:48 schreef MSalters het volgende:
Helpt dat dan?
De vraag is om een helderziende compiler. Dwz in Base::Base() moet er voorspeld worden of er een Derived object gemaakt gaat worden - wat dus met exceptions niet zeker is.
De vraag is een intelligente programmeur die aan de volgorde van de log-entries voldoende kan zien :)

Professionele website nodig?


  • Orphix
  • Registratie: Februari 2000
  • Niet online
Kan je niet runtime in C++ opvragen wat voor type het is?
Immers een dynamic_cast kan dit ook :?

  • The - DDD
  • Registratie: Januari 2000
  • Laatst online: 03-09 16:40
Wat zit iedereen nou vaag en ingewikkeld te doen.

Eerste reactie was al de goede.

Even een quote:
14.4.6.1 Exceptions en de initialisatie van members

Wat gebeurt er als een initialisatie van een member (direct of indirect) een exception opwerpt? Default wordt de exception doorgegeven naar de plek waar de constructor van de class (waar de member bij hoort) werd aangeroepen. De constructor kan echter zelf de exception opvangen, door de volledige functiebody (inclusief memberinitialisatielijts) in een tryblok te omsluiten. Bijvoorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class X {
   Vector v;
   //...
public:
   X(int);
   //...
);

X::X(inst s)
try
   :v(s)   //initialiseer v met s
{
   //...
}
catch(Vector::Size){ //Exception van v worden hier opgevangen
   //...
}
JA, dit is geldige syntax.

Nu kan je in de catch handler netjes checken of een bron geinitialiseerd is en vervolgens daar het nodige deleten en klaar. Netjes Leak vrij en de verantwoording ligt bij je class en niet bij de gebruiker van je class. Precies wat je wil. Als je nu in je catch handler de ook nog een exception gooit NADAT je de boel hebt opgeruimd dan weet de gebruiker van je class ook gelijk dat ie het object niet moet gebruiken omdat het prut is.

Geheugen gealloceerd door je orginele "new" aanroep wordt netjes vrijgegeven in geval van een exception. Alleen bij members die middels een "new" geinitialiseerd worden bestaat er kans op problemen, die je zoals hierboven beschreven kan omzeilen.

TENZIJ je gebruik maakt van placement. Dan is het een ander verhaal, dan geeft een exception in new niet automagisch het geheugen vrij. Als je niet weet wat het is? Kort gezegd betekend het dat je dan zelf bepaald waar een instantie geplaatst meot worden, bijvoorbeeld op een stuk buffer wat je in je stack hebt gealloceerd. Ik heb zelf nog nooit placement daadwerkelijk toegepast.

Bron: "De programmeertaal C++" van Bjarne Stroustrup

  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Allemaal hartelijk bedankt voor jullie hulp. Ik heb alleen zelf de (volgens mij) handigste oplossing nu gevonden:
code:
1
2
3
4
5
6
7
8
Sumpthing::Sumpthing()
{
  if(FoutjeGebeurd)
  {
    delete this;
    throw ExceptionObject("Foutje, maar opgeruimd!");
  }
}

Dan moet de destructor natuurlijk wel checken wat er zoal allemaal gemaakt is, maar dat is niet zo'n punt.
Werkt dit zo goed als ik tot nog toe heb ondervonden, of zie ik nog wat over het hoofd?

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • The - DDD
  • Registratie: Januari 2000
  • Laatst online: 03-09 16:40
Het enige wat je over het hoofd ziet is dat wanneer er een exception optreed in je initialisatie, dat er dan uit je constructor gesprongen wordt en dat je checks niet uitgevoerd worden.

Op zich werkt je oplossing prima, maar denk aan het geval dat er is een keer een exception optreed. Uiteraard moet je jezelf ook afvragen hoe robuust je je applicatie wil hebben.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:35

.oisyn

Moderator Devschuur®

Demotivational Speaker

Op zaterdag 11 mei 2002 13:35 schreef WildernessChild het volgende:
Allemaal hartelijk bedankt voor jullie hulp. Ik heb alleen zelf de (volgens mij) handigste oplossing nu gevonden:
code:
1
2
3
4
5
6
7
8
Sumpthing::Sumpthing()
{
  if(FoutjeGebeurd)
  {
    delete this;
    throw ExceptionObject("Foutje, maar opgeruimd!");
  }
}

Dan moet de destructor natuurlijk wel checken wat er zoal allemaal gemaakt is, maar dat is niet zo'n punt.
Werkt dit zo goed als ik tot nog toe heb ondervonden, of zie ik nog wat over het hoofd?
en als je nou eens een Sumpthing niet met new aanmaakt? Dan mag die delete this niet uitgevoerd worden, want dat kan voor behoorlijk foute dingen zorgen

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op zaterdag 11 mei 2002 15:40 schreef .oisyn het volgende:

[..]

en als je nou eens een Sumpthing niet met new aanmaakt? Dan mag die delete this niet uitgevoerd worden, want dat kan voor behoorlijk foute dingen zorgen
Erger nog: delete this is ook fout na new Sumpthing. 't Werkt alleen na een succesvolle return vanuit de ctor. En dus kun je nooit in de ctor een delete this doen.
De oplossing is simpel: de compiler zorgt ervoor dat alle members en alle bases gedelete worden bij een throw vanuit de ctor.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:35

.oisyn

Moderator Devschuur®

Demotivational Speaker

trouwens nog even een reactie op de try/catch syntax waar The - DDD het eerder over had...

Bij mij compilet het niet. Ik heb de syntax trouwens ook nog nooit eerder gezien... Het lijkt me ook niet logisch, want waarom zou je een exception van een superclass af kunnen vangen in z'n subclass :? (nou ja, technisch gezien niet in de subclass, maar toch)

Maar goed, dat even terzijde, ik geloof je op je woord als je zegt dat het correcte syntax is, maar bij mij werkt het dus niet. Of ligt dat gewoon weer aan de kuren van de MSVC++ compiler?

.edit: laat maar, ik heb het antwoord al:
Microsoft C++ does not support exception-specifications, as described in section 15.4 of the ANSI C++ draft. In addition, it does not support function-try-block described in section 15 of the ANSI C++ draft.
|:(

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op zaterdag 11 mei 2002 00:21 schreef The - DDD het volgende:
Wat zit iedereen nou vaag en ingewikkeld te doen.

Eerste reactie was al de goede.

Even een quote:
[... function-try block ...]

JA, dit is geldige syntax.

Nu kan je in de catch handler netjes checken of een bron geinitialiseerd is en vervolgens daar het nodige deleten en klaar. Netjes Leak vrij en de verantwoording ligt bij je class en niet bij de gebruiker van je class. Precies wat je wil. Als je nu in je catch handler de ook nog een exception gooit NADAT je de boel hebt opgeruimd dan weet de gebruiker van je class ook gelijk dat ie het object niet moet gebruiken omdat het prut is.
Gebeurt automatisch; een ctor function-try block exit met een throw; , net zoals een functie exit met een return;

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
Dus als ik het goed begrijp moet ik het gewoon zo schrijven:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
Class::Class():
 BaseClass()
{
  try
  {
    // code die kan gooien
  }
  catch(...)
  {
    this->~Class();
    throw; // doorgooien
  }
}

Dan dus wel met een construction-aware destructor.
Waarom kan ik deze trouwens niet gewoon met ~Class() aanroepen, maar moet er this voor? Of begrijp ik het nu weer verkeerd??

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 12:35

.oisyn

Moderator Devschuur®

Demotivational Speaker

ouch

de destructor van de baseclass wordt automatisch al aangeroepen bij een exception in de constructor. Je moet dus gewoon dit doen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class::Class () : BaseClass ()
{
    try
    {
      // doe wat initialisatie, bijvoorbeeld:
      array = NULL;
      array = new int[100];
      functieDieEenExceptionKanThrowen ();
    }
    catch (...)
    {
      // ruim op wat opgeruimd moet worden, dus:
      if (array)
        delete[] array;
      throw;
    }
}

(zorg dus wel dat array een null waarde heeft voordat je m initialiseert met een nieuwe waarde, aangezien de new een exception kan throwen. En als new een exception throw't, dan is de waarde van array onveranderd, dus zonder m eerst te initialiseren wijst het naar random data, en dan gaat de delete[] de fout in)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • WildernessChild
  • Registratie: Februari 2002
  • Niet online

WildernessChild

Voor al uw hersenspinsels

Topicstarter
In feite doe je het destructeren dus op twee plekken: in de constructor als er een exception optreedt en in de destructor. Ik dacht altijd dat als je twee keer dezelfde code gebruikt, dat die dan in een functie gezet hoort te worden? En als je nou al zo'n functie hebt, genaamd destructor, waarom zou je die dan niet aanroepen? Dan worden de destructors van de baseclasses toch niet ook aangeroepen, dat gebeurt pas als de exception uit de constructor gegooid wordt. Resultaat: derived class opgeruimd + base class opgeruimd = geen leaks. Toch?

Maker van Taekwindow; verplaats en resize je vensters met de Alt-toets!


  • MSalters
  • Registratie: Juni 2001
  • Nu online
Op donderdag 16 mei 2002 19:45 schreef WildernessChild het volgende:
In feite doe je het destructeren dus op twee plekken: in de constructor als er een exception optreedt en in de destructor. Ik dacht altijd dat als je twee keer dezelfde code gebruikt, dat die dan in een functie gezet hoort te worden? En als je nou al zo'n functie hebt, genaamd destructor, waarom zou je die dan niet aanroepen? Dan worden de destructors van de baseclasses toch niet ook aangeroepen, dat gebeurt pas als de exception uit de constructor gegooid wordt. Resultaat: derived class opgeruimd + base class opgeruimd = geen leaks. Toch?
Je hebt 99% gelijk. Alleen doet de dtor in je huidige class net iets meer.
De standaard oplossing is dus om geen new/delete in je eigen class te doen, maar een std:: library class te gebruiken o.i.d. Die dtor doet wel precies genoeg.

Je tweede hypothese klopt niet; de enige manier om betrouwbaar onder een mislukt object uit te komen is om een exception uit de ctor te gooien. En dan worden alle succesvolle ctors tot dan toe gecanceled, door de bijbehorende dtors aan te roepen. Als je zelf een dtor gaat aanroepen, op een moment waar het sowieso nog niet mag, dan gaat de compiler daar echt geen consequenties aan verbinden.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1