[C++] Standaard implementatie voor stack guard?

Pagina: 1
Acties:

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Ik heb net de volgende class geschreven:

C++:
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Cleanup functor oject must have a non-throwing assignment operator
// The cleanup function must not throw (as this will cause an exception in dtor)
template <typename Init, typename Cleanup>
class Sentry {
    Cleanup clean_;
    bool owns_;
public:
    Sentry() : owns_(false) {}

// if either Cleanup's cctor throws or initialize() throws, all is well since cleanup is never called.
    Sentry(Init initialize, Cleanup c) : clean_(c), owns_(true) {
        initialize();
    }

    ~Sentry() {
        if (owns_) clean_();
    }

// if cleanup's cctor throws all is well, we never take ownership. This might cause an object to be lost
// but at least  a proper exception notifies us in that case
    Sentry(Sentry& src) : clean_(src.clean_), owns_(src.owns_)  {
        src.owns_ = false;
    }

// this is not perfect...if (err assignment operator) throws...havoc    
const Sentry& operator=(Sentry& src) {
        if (owns_) clean_();
        clean_ = src.clean_; // must not throw
        owns_ = true;
        src.owns_ = false;
    }
};

// 2nd implementation, safe but slow?
template <typename I, typename C>
class Sentry2 {
    class X_ {
        C c_;
    public:
        X_(C c) : c_(c) {}
        ~X_() {c_();}
    };
    std::auto_ptr<X_> xptr_;
public:
// if new throws, all is well, no functors called, no object created
// problem with i(), if it throws, c() would be called, so catch  a possible exception and remove our ownership
    Sentry2(I i, C c) : xptr_(new X_(c)) {
        try {
            i();
        } catch (...) {
            xptr_.reset();
            throw;
        }
    }
    ~Sentry2() {}
};

// usage:
typedef Sentry<boost::function<void ()>, boost::function<void ()> > DisableGuard;
CGContext::DisableGuard CGContext::guardedEnable() {
    return CGContext::DisableGuard(
        boost::bind(&CGContext::enable, this),
        boost::bind(&CGContext::disable, this)
    );
}

De eerste code is efficienter, maar heeft een probleem met de assignment operator=. Hier kan je volgens mij niet omheen, tenzij je een pointerwise kopie zou kunnen maken, en die dan in swappen. (copy-swap idiom)

De tweede lijkt me robuust onder elk geval, maar is wel redelijk traag denk ik. Waarschijnlijk te traag voor bv het locken van een variabele in een loop.

Is hier geen standaard object voor? Iets in boost misschien?

[ Voor 3% gewijzigd door Zoijar op 06-07-2005 12:36 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

[rant]
In plaats van dtor troep zie ik liever gewoon support voor try/finally in C++ :/
[/rant]

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.


  • Eelis
  • Registratie: Januari 2003
  • Laatst online: 21-02-2015
.

[ Voor 105% gewijzigd door Eelis op 18-02-2015 19:03 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Eelis schreef op woensdag 06 juli 2005 @ 14:33:
Zoijar:
Je bent nogal vaag over je ontwerpdoelen. Je Sentry lijkt zo te zien op een Alexandrescu scope guard, maar waarom wil je 'm copyable/assignable hebben? En wat is het nut van de initialize constructor parameter? En waarom is de hele class geparameteriseerd op Init?
Hij moet copyable zijn zodat ik sentries kan retourneren vanuit functies. Een class heeft bv een public member 'enable' die een sentry retourneerd met als initializatie de virtuele member enable() en als cleanup de virtuele member disable(). Het is voor clients nu vrijwel onmogelijk om de class te enablen zonder deze weer te disablen. Je moet wel op life times letten, dat de class langer bestaat dan de sentry, maar dat is iha geen probleem.

Ik zou ook een auto_ptr naar een free store allocated non-copyable sentry oject kunnen retourneren. Dat is ook wel een idee. Dat is in feite implementatie 2.

Ik zou de ctor kunnen parameteriseren op Init, maar dit lijkt me toch netter. Ik wil geen restrictie tot boost::function, dus het moeten templates zijn. In dat geval kan je ook bv een normale functie pointer gebruiken. Hoewel het misschien wel een idee is om boost::function te hard coden, je kan toch vrijwel alles hier naar toe omzetten. Alleen leg je dan de restrictie op dat boost gebruikt moet worden.

Ik had specifiek geen try/catch in de dtor omdat ik veronderstel dat een cleanup functie (of dtor) nooit throwed. Maar het kan idd geen kwaad om er zeker van te zijn.

Ohja, loki had ook ziets geloof ik. Tijd geleden al dat boek... zal nog eens kijken. Hoewel ik niet heel loki wil importeren, dus ik schrijf het dan toch zelf. Boost zou nog net kunnen.

oh, ik nam een init() functie erin op, om het een atomisch paar te maken. Bij jouw implementatie, als je eerst een resource allocate, en dan je stackguard aanmaakt, kan het gebeuren dat je stackguard ctor throwed. In dat geval zal je dtor nooit draaien, en leak je je resource. Een algemene functor ctor kan goed throwen, bv als er geheugen wordt gealloceerd oid.

[ Voor 12% gewijzigd door Zoijar op 06-07-2005 16:18 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
.oisyn schreef op woensdag 06 juli 2005 @ 13:23:
[rant]
In plaats van dtor troep zie ik liever gewoon support voor try/finally in C++ :/
[/rant]
[rant]OMDB[/rant]
Wie dtors heeft, heeft finally niet nodig. ScopeGuard is veel flexibeler, omdat je 'm op elke plek kunt starten. Met try/finally komt er bij elke actie een blockscope bij.

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: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ten eerste sluit ondersteuning van finally het gebruik van dtors niet uit (wellicht vat je m'n post verkeerd op? Dtors zijn zeker A Good Thingtm, alleen voor willekeurig opruimcode, zoals bijvoorbeeld rollbacks van DB transacties als er iets fout gaat, werkt het gewoon irritant als die opruimcode wat complexer wordt). Ten tweede blijf je met vage scopeguards altijd functies in een andere scope aanroepen, met als gevolg dat je gewoon niet bij de lokale parameters van die functie kan en dus alles een beetje mee moet gaan zitten passen naar random objecten. Ik snap dat je C++ zealot bent, ik ook, maar mijn ervaring is dat finally gewoon lekkerder werkt, ook al kun je er vrijwel exact hetzelfde mee.

Daarnaast wordt je van scopeguards die je op random plekken in je code zet ook niet vrolijk. Je moet ze ten eerste in de omgekeerde volgorde van je initialisatie definieren, daarnaast zul je met dismiss functionaliteit zoals Eelis bijvoorbeeld definieert moeilijk moeten gaan doen om die dismisses te cascaden naar de rest van de scopeguards. Dus, ik blijf lekker hopen, ook al moet het over jouw dooie lijf ;)

[ Voor 13% gewijzigd door .oisyn op 06-07-2005 17:36 ]

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...

Topicstarter
finally heeft ook nog een rij problemen hoor, voornamelijk als een onderdeel van een ctor throwed, zoals de private base class, of een member. Moet je die dan opruimen in een finally? Nee, want je kan er niet eens bij na een exception. Dus alsnog twee verschillende stukken code, of boolean checks (hasBeenDeleted)...

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Hoe kan je een object dat throw't in de constructor überhaupt "opruimen", afgezien met een function try block? Dat hoeft dus ook niet, noch met finally, noch met dtors.

Niet dat ik er per se finally in wil hebben hoor, native lambda expressions oid zouden ook handig zijn. Ik blijf die scopeguards hackery vinden (evenals boost::lambda overigens, het blijft een workaround voor iets dat veel beter native in de taal geimplementeerd kan worden ;))

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
  • Laatst online: 09-04 22:08
.oisyn schreef op woensdag 06 juli 2005 @ 17:34:
Ten eerste sluit ondersteuning van finally het gebruik van dtors niet uit (wellicht vat je m'n post verkeerd op? Dtors zijn zeker A Good Thingtm, alleen voor willekeurig opruimcode, zoals bijvoorbeeld rollbacks van DB transacties als er iets fout gaat, werkt het gewoon irritant als die opruimcode wat complexer wordt). Ten tweede blijf je met vage scopeguards altijd functies in een andere scope aanroepen, met als gevolg dat je gewoon niet bij de lokale parameters van die functie kan en dus alles een beetje mee moet gaan zitten passen naar random objecten. Ik snap dat je C++ zealot bent, ik ook, maar mijn ervaring is dat finally gewoon lekkerder werkt, ook al kun je er vrijwel exact hetzelfde mee.
try { } is ook een scope, waar je dus in je finally niet meer bij kunt. Logisch, ik weet niet waar in de try { } een throw gebeurd is. Dat kan de initialisatie van zo goed als elk object uit de try { } overslaan. Die objecten zijn dus onbruikbaar in de finally { } scope.

Een ScopeGuard daarentegen is gewoon een object, met members. Ik kan dus objecten aan een lijst toevoegen.

Toegegeven, je zult iets als boost::bind nodig hebben. De huidige <functional> maakt het niet makkelijk genoeg.

Alleen je voorbeeld van een BD is ongelukkig. Dat hoort eenvoudigweg een RAII object te zijn. dtor doet een rollback, tenzij .bComitted==true. Zie std::fstream
Daarnaast wordt je van scopeguards die je op random plekken in je code zet ook niet vrolijk. Je moet ze ten eerste in de omgekeerde volgorde van je initialisatie definieren, daarnaast zul je met dismiss functionaliteit zoals Eelis bijvoorbeeld definieert moeilijk moeten gaan doen om die dismisses te cascaden naar de rest van de scopeguards. Dus, ik blijf lekker hopen, ook al moet het over jouw dooie lijf ;)
Ik mis iets? Scopeguards definieer je uiteraard in omgekeerde volgorde van uitvoering (heb je nu eenmaal met dingen die op de stack staan) maar de definitie is dus wel in de volgorden van initialisatie. Typisch RAII. En dismiss is iets wat wel met scope guard werkt, maar niet met finally. Overigens twijfel ik aan de wenselijkheid van dismiss. Het voordeel van een ScopeGuard zonder, is dat je weet dat die gaat gebeuren.

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


  • Eelis
  • Registratie: Januari 2003
  • Laatst online: 21-02-2015
.

[ Voor 99% gewijzigd door Eelis op 18-02-2015 19:04 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
Of voor het initializeren van twee resources in 1 functie. Hoewel je zoiets ook met een try/catch kan doen, vaak is het makkelijk om het in een vorm als deze te schrijven:

code:
1
2
3
4
5
6
7
8
Guard xg(allocate resource X);
Guard yg(allocate resource Y);

// do some stuff here that might throw (allocations could throw as well!) if it does, free guarded resources

// Ok, all done. Everything's cool, now our class has definite  ownership in its dtor, so dismiss
xg.dismiss(); yg.dismiss();
}


(eigenlijk hetzelfde ja, ook rollback/commit semantics)

[ Voor 7% gewijzigd door Zoijar op 07-07-2005 16:05 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Initializeren van twee resources in een functie gebeurt nooit, als je RAII gebruikt. Elke resource is dan een class, en die ruimen zichzelf netjes op. Denk aan std::string, daar ga je ook geen ScopeGuards gebruiken als je er twee in een functie hebt.

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


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
MSalters schreef op donderdag 07 juli 2005 @ 17:14:
Initializeren van twee resources in een functie gebeurt nooit, als je RAII gebruikt. Elke resource is dan een class, en die ruimen zichzelf netjes op. Denk aan std::string, daar ga je ook geen ScopeGuards gebruiken als je er twee in een functie hebt.
Ja daar heb je wel gelijk in.

Toevallig was ik net hieraan bezig:

C++:
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
template <typename T>
class ResourceGuard : public boost::noncopyable {
public:
    typedef boost::function<T ()> InitFunctor;
    typedef boost::function<void (T)> CleanFunctor;
    typedef std::auto_ptr<ResourceGuard<T> > AutoPtr;

    ResourceGuard(const InitFunctor& i, const CleanFunctor& c) :
        clean_(c), obj_(i())
    {
    }
    
    ~ResourceGuard() {
        try {
            clean_(obj_);
        } catch(...) {}
    }

    T& getObject() {
        return obj_;
    }

    const T& getObject() const {
        return obj_;
    }

private:
    CleanFunctor clean_;
    T obj_;
};

// usage:
class X {
// ...
private:
    ResourceGuard<CGcontext> context_;
};

X::X() : context_(
    boost::function<CGcontext ()>(&cgCreateContext),
    boost::function<void (CGcontext)>(&cgDestroyContext)) 
{
   // do stuff that can throw
}


Bruikbaar? Nuttig? Commentaar? :)

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Misschien is het leuk om een toelichting te typen voor iets minder gevorderde C++-programmeurs, waarin je uitlegt wat een stack-guard is, waarom je 'm nodig hebt, wanneer je 'm kunt gebruiken, en hoe precies?

De mensen die in dit topic reageren zijn .oisyn, eelis en MSalters, naar mijn idee allemaal die-hard-C++-programmeurs. Als je het iets toegankelijker maakt kunnen de mindere goden er misschien wat van leren. ;)

  • Eelis
  • Registratie: Januari 2003
  • Laatst online: 21-02-2015
.

[ Voor 99% gewijzigd door Eelis op 18-02-2015 19:04 ]


  • Sybr_E-N
  • Registratie: December 2001
  • Laatst online: 21:37
Soultaker schreef op donderdag 07 juli 2005 @ 21:03:
Misschien is het leuk om een toelichting te typen voor iets minder gevorderde C++-programmeurs, waarin je uitlegt wat een stack-guard is, waarom je 'm nodig hebt, wanneer je 'm kunt gebruiken, en hoe precies?

De mensen die in dit topic reageren zijn .oisyn, eelis en MSalters, naar mijn idee allemaal die-hard-C++-programmeurs. Als je het iets toegankelijker maakt kunnen de mindere goden er misschien wat van leren. ;)
Held, ik wou gisteren hetzelfde posten aangezien ik het idee van een stack-guard/ScopeGuard nog niet helemaal voor me zie.
Eelis schreef op donderdag 07 juli 2005 @ 21:12:
[...]
Alexandrescu legt 't allemaal uit in dit artikeltje. :)
Die site ben ik ook tegen gekomen op mijn zoektocht naar een goede beschrijving. Al gaat dat artikel er voor mij aardig diep op te implementatie in. Ik ben eigenlijk opzoek waarin er meer wordt uitgelegd.
Zoals Soultaker al zei: wat een stack-guard is, waarom je 'm nodig hebt, wanneer je 'm kunt gebruiken, en hoe precies.

[ Voor 13% gewijzigd door Sybr_E-N op 08-07-2005 13:54 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Exceptions worden gegooid when you least expect it (hence de naam "exception" ;)), en een correcte applicatie bevindt zich altijd in een consistente staat. Om het voorbeeld van die webpagina aan te houden: stel je hebt een lijst in geheugen en in een database, en je wilt dat die twee altijd synchroon met elkaar lopen. Helaas is het updaten van zowel de lijst als de database geen atomaire actie, bij het updaten van een van de twee kan er een exception optreden, en het is nogal vervelend als dat gebeurt bij de ander terwijl je de een al geupdate hebt (er is immers geen consistente staat meer, er staat een entry in de lijst die niet in de database staat, of andersom).

Je code moet op het gooien van exceptions berekend zijn, en in het simpelste geval betekent dat simpelweg alle exceptions afvangen, en dingen terugdraaien als dat gebeurt, om vervolgens de exception door te gooien naar een hoger niveau in je applicatie waar de error verder afgehandeld wordt. Het gebruik van try/catch blokken in je applicatie zorgt nogal voor codebloat, iets wat je liever wilt voorkomen als het net zo goed op een andere manier kan.

Een van de punten waar C++ zich imho sterk in onderscheid tov andere talen is de combinatie van stack unwinding en destructors. Objecten die op de stack worden aangemaakt moeten ook weer worden opgeruimd zodra ze buiten scope gaan. Ze staan immers op de stack, en als ze buiten scope gaan is de stack space weg en bestaat het object dus niet meer. Resources (zoals geheugen) die door het object mogelijk vastgehouden worden moeten dus op dat moment ook vrijgegeven worden, omdat je programma anders resources lekt. Dit moet ook gebeuren als exceptions gegooid worden, en dat gebeurt gelukkig ook. Hier kun je heel handig gebruik van maken, een stuk code in de destructor van een object wordt altijd uitgevoerd als dat object op de stack is aangemaakt en het object gaat buiten scope. In plaats van die vervelende try/catch blokken waarin je exceptions afvangt en doorgooit, met opruimcode ertussen, kun je dus ook objecten aanmaken met de relevante opruimcode in de destructor die _automatisch_ aangeroepen wordt, met slechts 1 regel C++ code (de definitie van de variabele in de scope).

Nou kent C++ niet de zogenaamde lambda expressions; het in-place definieren van een functie met code erin, maar daar is met template hackery wel enigszins omheen te werken (zie boost::lambda). Maar wat de scopeguards classes getoond in deze draad precies doen is simpelweg het aanroepen van een functie op het moment dat het object (de scopeguard) wordt opgeruimd. In zo'n functie definieer je dan wat er aan opruimwerk moet gebeuren, in het geval van het voorbeeld dus het verwijderen van het net toegevoegde element aan de lijst als blijkt dat het toevoegen aan de database een fout oplevert.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void User::AddFriend(User& newFriend)
{
    // voeg entry toe aan de lijst in het geheugen:
    friends_.push_back(&newFriend);

    // definitie van de scopeguard (kom ik nog op terug)
    ScopeGuard guard = MakeObjGuard (friends_, &UserCont::pop_back);

    // toevoegen van de entry aan de database
    pDB_->AddFriend(GetName(), newFriend.GetName());

    // all is well, zorg dat het verwijderen uit de lijst niet meer plaatsvindt
    guard.Dismiss();
}


Als er bij het toevoegen aan de lijst een exception gegooid wordt is er niets aan de hand, je applicatie is immers in een consistente staat. Het aanmaken van het ScopeGuard object zorgt ervoor dat het element wat zojuist is toegevoegd weer uit de lijst verwijderd wordt (dmv een call naar friends.pop_back()), zodra dat object weer opgeruimd wordt. Vervolgens wordt het element aan de database toegevoegd. Dit kan ook weer een exception gooien, en dit moment is cruciaal: als je deze exception compleet negeert heb je dus een entry in de lijst die niet in de database staat, en is je applicatie dus in een inconsistente staat. Is het toevoegen wel gelukt, dan kan er niets meer fout gaan, en kun je de scopeguard 'dismissen'. Dit natuurlijk omdat het element anders alsnog weer uit de lijst verwijderd wordt, terwijl dat niet meer de bedoeling is.

[ Voor 5% gewijzigd door .oisyn op 08-07-2005 14:25 ]

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.


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 22:23
Wel oppassen trouwens, als je een rijtje van die scopeguards hebt, en je vergeet er eentje te dismissen heb je nog een inconsistente state ....

Wat ik ook een beetje oneconomisch vind aandoen is dat je voor elke object die je in je atomaire bewerking hebt zitten een tweede object moet bijmaken.

[ Voor 36% gewijzigd door farlane op 08-07-2005 15:27 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Beide punten die je noemt zijn wel op te lossen. Zoals Zoijar de draad begonnen is heeft hij een scopeguard met init functionalitait; dan hou je slechts 1 object per atomaire actie. En het probleem met de dismiss is natuurlijk op te lossen door cascading functionaliteit toe te voegen; geef de vorige scopeguard mee met de definitie van de volgende scopeguard, en zorg dat een dismiss op een scopeguard ook z'n "parent" dismissed.

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...

Topicstarter
Mooie uitleg :)

Een ander veel voorkomend voorbeeld is dit:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class X {
   Resource *p1_, *p2_;
public:
   // zet de pointers eerst op nul. alloceer daarna een resource
   X()  {
      p1_ = AcquireResource();
      p2_ = AcquireResource();
}

   // wis gealloceerde resources, een null pointer freeen is niet erg.
   ~X() {
      FreeResource(p1_);
      FreeResource(p2_);
   }
};

Zie je wat hier fout gaat? ... Stel dat bij de allocatie voor p2_ de AcquireResource een exception throws (geen vrije sockets meer bv). In dat geval zal de dtor van X nooit draaien, en zal de resource p1_ nooit worden vrijgegeven! Dit kan je oplossen met de bovenstaande class, dan wordt het als volgt:

C++:
1
2
3
4
5
6
7
8
9
10
11
class X {
   ResourceGuard<Resource*> p1_, p2_;
public:
   // zet de pointers eerst op nul. alloceer daarna een resource
   X() : p1_(boost::function<Resource* ()>(AcquireResource), boost::function<void (Resource*)>(FreeResource)), p2_(boost::function<Resource* ()>(AcquireResource), boost::function<void (Resource*)>(FreeResource))
 {
}

   // geen code meer nodig om iets te wissen
   ~X() {}
};

Nu gaat alles goed. Als namelijk de Acquire call voor p2_ fout gaat en een exception throws, dan is de resourceguard van p1_ al een "fully constructed object", en C++ garandeert dat bij het verlaten van de constructor vanwege een exception, al dit soort objecten opgeruimd worden dmv het aanroepen van hun dtor. En die dtor roept netjes FreeResource aan voor p1.

Wat geberut er als de ctor van het ResourceGuard object van p1 een exception throws? Bv omdat de initializatie functie throws? Dan wordt de ResourceGuard ctor verlaten dmv een exception, en zal het nooit een fully constructed object worden; ie. de dtor zal nooit draaien, en dus als AcquireResource faalt, zal FreeResource nooit worden aangeroepen.

Er zit hier wel potentieel een probleem. Zie je het? In de ctor van resourceguard? Stel dat het niet om een pointer gaat, maare om een algemeen object T. Stel nu dat de initializatie goed gaat, maar de copy constructor van het T object (obj_(i())) gooit een exception (geen geheugen meer?)? Dan zal de opruim code nooit draaien, en leak je een resource. Ondanks al ons werk. Maar hetzelfde geval zou zich voordoen als we de code handmatig schreven, met een try/catch oid.
farlane schreef op vrijdag 08 juli 2005 @ 15:26:
Wat ik ook een beetje oneconomisch vind aandoen is dat je voor elke object die je in je atomaire bewerking hebt zitten een tweede object moet bijmaken.
Een try/catch block kost je waarschijnlijk meer. Exceptions handling is duur :)

Dit is eigenlijk een leuker voorbeeld:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
class X {
   Resource &p1_, &p2_;
public:
   // references MOETEN in de initializer list geinit worden...de aqcuire levert een ref naar een intern object
   X() : p1_(AcquireResource()), p2_(AcquireResource())  {
}
   // wis gealloceerde resources
   ~X() {
      FreeResource(p1_);
      FreeResource(p2_);
   }
};

Daar we het vorige probleem nog simpel op konden lossen met:
C++:
1
2
3
4
5
6
   // zet de pointers eerst op nul. alloceer daarna een resource
   X()  {
      p1_ = AcquireResource();
      try {p2_ = AcquireResource();}
      catch(...) {FreeResource(p1_); throw;}
}

Gaat dat hier niet meer op.
C++:
1
2
3
   X() try : 
      p1_(AcquireResource()), p2_(AcquireResource())  {
} catch (...) {FreeResource(p1_); FreeResource(p2_); throw;}

is namelijk niet legal. Je hebt geen toegang meer tot p1_ of p2_ in je catch.

[ Voor 25% gewijzigd door Zoijar op 08-07-2005 16:16 ]


  • RobLemmens
  • Registratie: Juni 2003
  • Laatst online: 12-04 17:37
"Exceptions handling is duur"

Correct me if i'm wrong maar worden ze niet enkel duur wanneer ze ge-throwed worden?

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Topicstarter
RobLemmens schreef op vrijdag 08 juli 2005 @ 16:16:
"Exceptions handling is duur"
Correct me if i'm wrong maar worden ze niet enkel duur wanneer ze ge-throwed worden?
Nee, dat is een standaard misvatting. Het simpelweg aan hebben staan van 'exceptions' kost je al aanzienlijk. Dat komt omdat je stack bijgehouden moet worden etc. Omdat alles 'kan' gooien. Exception specificaties lossen dat niet op, maar maken het juist nog erger, omdat er dan gekeken moet worden of unexpected() aangeroepen moet worden.

offtopic:
Speel jij trouwens magic the gathering? Klant bij nedermagic? Je naam komt me bekend voor; vaak kaarten aan je gestuurd?

[ Voor 15% gewijzigd door Zoijar op 08-07-2005 16:23 ]


  • RobLemmens
  • Registratie: Juni 2003
  • Laatst online: 12-04 17:37
Zoijar schreef op vrijdag 08 juli 2005 @ 16:18:
offtopic:
Speel jij trouwens magic the gathering? Klant bij nedermagic? Je naam komt me bekend voor; vaak kaarten aan je gestuurd?
offtopic:
Nope.. Er lopen veel Rob Lemmensen in nederland rond.

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 22:23
.oisyn schreef op vrijdag 08 juli 2005 @ 15:47:
Beide punten die je noemt zijn wel op te lossen. Zoals Zoijar de draad begonnen is heeft hij een scopeguard met init functionalitait; dan hou je slechts 1 object per atomaire actie.
Het probleem is dattie altijd zoveel < en >'en gebruikt in zijn code :) Maw, ik zie dat ( met mijn 'non godlike' C++ kennis) eerlijk gezegd niet terugkomen .... :)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoijar schreef op vrijdag 08 juli 2005 @ 15:55:
Een try/catch block kost je waarschijnlijk meer. Exceptions handling is duur :)
En hoe denk je dat een compiler z'n stack unwinding code registreert? Dat is effectief hetzelfde als een catch all phrase ;)
farlane schreef op vrijdag 08 juli 2005 @ 16:53:
[...]


Het probleem is dattie altijd zoveel < en >'en gebruikt in zijn code :) Maw, ik zie dat ( met mijn 'non godlike' C++ kennis) eerlijk gezegd niet terugkomen .... :)
Tja dat gebeurt al snel bij dingen als boost::bind etc. ;). Z'n resource voorbeeld vind ik ook wat lichtelijk overdreven; ik zou in zo'n geval een wrapper class gemaakt hebben om de resource heen, een soort van smartpointer. Misschien gebaseerd op een template, maar dan wel met een handig te gebruiken typedef. En zo'n smart- of autopointer zorgt er zelf voor dat resources in z'n destructor gewoon vrijgegeven worden.

[ Voor 54% gewijzigd door .oisyn op 08-07-2005 17:01 ]

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
  • Laatst online: 09-04 22:08
Zoijar schreef op vrijdag 08 juli 2005 @ 16:18:
[...]
Nee, dat is een standaard misvatting. Het simpelweg aan hebben staan van 'exceptions' kost je al aanzienlijk. Dat komt omdat je stack bijgehouden moet worden etc. Omdat alles 'kan' gooien. Exception specificaties lossen dat niet op, maar maken het juist nog erger, omdat er dan gekeken moet worden of unexpected() aangeroepen moet worden.
Valt wel mee hoor, die "misvatting". Er zijn implementaties, met name van Sun, die geen enkele overhead hebben zolang er geen exceptions gegooid worden. De stack moet sowieso toch bijgehouden worden, met name rondom funciton calls. Exceptions die vanuit andere functies komen zijn daarom al gratis. Alle plekken waarop een exception daadwerkelijk gegooid wordt zijn bekend. Op die plaatsen heb je (logisch) een branch, waar de compiler ook nog een prediction aan kan hangen. Die is daarmee dus ook feitelijk gratis. De code waar je naar toe springt zit in een demand-loaded segment en heeft dus geen memory impact in de normale situatie. Pas bij een exceptie wordt de unwind informatie voor alle functies op de stack geladen.

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: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Maar ja, dat is Sun ;). Wij compileren onze games (MSVC++ voor PC, Xbox en Xbox 360; GCC voor PS2 en Gamecube) altijd met exceptions uitgeschakeld door de extra overhead die het met zich meebrengt. Nou is er behalve standaard io in een game sowieso al niet echt een reden om exceptions te gooien, maar dat terzijde :), de io zelf doen we gewoon met old fashioned error checking.

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.


  • alienfruit
  • Registratie: Maart 2003
  • Laatst online: 00:59

alienfruit

the alien you never expected

Ik neem aan dat jullie ook nog steeds VS C++ 6 gebruiken? Voor het maken van plugins voor Director, AE, Digital Fusion zit ik vastgebonden aan VS C++ 6. Erg jammr, want VS6 sucks een beetje :+

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 05-05 18:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Vanwaar die aanname? Zeker als je bedenkt dat xbox een MS platform is en je derhalve dus wel een beetje gedwongen wordt te updaten :). Max en maya plugins zijn prima te doen met vs.net 2003, dus wat dat betreft geen probleem hier. Maar dit is allemaal nogal offtopic, als je meer wilt weten kun je me bereiken op het e-mail adres dat in mijn profile staat vermeld ;)

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...

Topicstarter
farlane schreef op vrijdag 08 juli 2005 @ 16:53:
Het probleem is dattie altijd zoveel < en >'en gebruikt in zijn code :) Maw, ik zie dat ( met mijn 'non godlike' C++ kennis) eerlijk gezegd niet terugkomen .... :)
Speciaal voor jou wat haken erbij in de ctor, zodat je ze niet meer in je code nodfig hebt ;)

C++:
1
2
3
4
5
6
7
8
9
10
// new ctor
    template <typename I, typename C>
    ResourceGuard(const I& i, const C& c) :
        clean_(c), obj_(i())
    {
    }
// might add this, but I dont like it:
   operator T() {
      return _obj;
   }

Nu is het gebruik wel erg simpel: ResourceGuard<Resource*> rg_(&init, &clean);

Anyway, de rede dat ik zoveel haken gebruik is dat als je toch eenmaal iets schrijft, je het net zo goed algemeen kan maken. Het lijkt misschien ingewikkled, maar dit is echt redelijk simpel. Geen rare template truuks oid.
MSalters schreef op zaterdag 09 juli 2005 @ 16:54:
Valt wel mee hoor, die "misvatting". Er zijn implementaties, met name van Sun, die geen enkele overhead hebben zolang er geen exceptions gegooid worden. De stack moet sowieso toch bijgehouden worden, met name rondom funciton calls. Exceptions die vanuit andere functies komen zijn daarom al gratis. Alle plekken waarop een exception daadwerkelijk gegooid wordt zijn bekend. Op die plaatsen heb je (logisch) een branch, waar de compiler ook nog een prediction aan kan hangen. Die is daarmee dus ook feitelijk gratis. De code waar je naar toe springt zit in een demand-loaded segment en heeft dus geen memory impact in de normale situatie. Pas bij een exceptie wordt de unwind informatie voor alle functies op de stack geladen.
Ok, compilers zullen wel steeds beter worden. Ik weet alleen wat ik erover heb gelezen, ik heb zelf nooit tests uitgevoerd. Vaak gelezen dat exception handling niet alleen iets kost als je een exception gooit, en dus gemiddeld niet niets kost.
alienfruit schreef op maandag 11 juli 2005 @ 11:25:
Ik neem aan dat jullie ook nog steeds VS C++ 6 gebruiken? Voor het maken van plugins voor Director, AE, Digital Fusion zit ik vastgebonden aan VS C++ 6. Erg jammr, want VS6 sucks een beetje :+
Een beetje is een understatement. Ik zou het niet eens een C++ compiler durven noemen... anyway, je gebruikt toch ook geen windows 3.11 meer? :P .NET 2003 (7.1) is wel weer ok...7.0 zuigt ook een beetje.
Pagina: 1