Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

RAII for check-in/check-out

2 views
Skip to first unread message

Mary K. Kuhner

unread,
Oct 11, 2001, 8:13:44 AM10/11/01
to
I recently asked for help with a design problem, and have settled
on a solution that involves checking out a copy of something,
modifying it, and checking it back in (transferring the changes
to the original).

One poster suggested using RAII for this, encapsulating the
task in an object which makes the copy in its constructor and
does the copying back onto the original in its destructor. This
sounds very neat and elegant, but in implementing it I have
encountered two problems:

(1) If an exception is thrown while the copy is checked
out, the destructor will be called and will attempt to check
it back in. This is not a problem for my current program,
which tends to treat all exceptions as terminal, but it
seems like a future problem, when I get around to trying to
give the program recovery capability. A check-in of a
partially modified, possibly broken structure sounds like
a bad idea. But I don't see how I can stop the check-in.
(Testing to see if an exception is active?)

(2) How do I provide const access? Some parts of the program
really need to see this stuff, but they do not need to
change it. Giving them write access violates const correctness.
But I can't see how to make an object that copies in its
constructor and checks-in in its destructor *except* when
it's const, in which case it only copies in its constructor.

I could obviously write a second interface that allows const
access, but there's a lot of redundant code involved.
Inheritance of implementation looms as a possibility, but
I don't really care for it.

I'm sure lots of people have coded objects like this. Any
advice on these problems? I'm almost ready to abandon
RAII here and go back to object.CheckOut() and object.CheckIn().
But I really like RAII's security against forgetting to
call CheckIn().

Mary Kuhner mkku...@eskimo.com

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Carl Daniel

unread,
Oct 11, 2001, 11:37:48 AM10/11/01
to

"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote in message
news:9q1vgm$2170$1...@nntp6.u.washington.edu...
[ship of cogent description of problem]

>
> I'm sure lots of people have coded objects like this. Any
> advice on these problems? I'm almost ready to abandon
> RAII here and go back to object.CheckOut() and object.CheckIn().
> But I really like RAII's security against forgetting to
> call CheckIn().
>

It's an interesting problem for sure!

I'd probably try to split the "RAII" class into two classes: a "Check Out"
class, and a "Check In" class. The CheckOut class would obtain a read-only
copy of the data and provide const access to it. It would do nothing
(except possibly release a lock, decrement a count, etc) in it's destructor.

The CheckIn class would take an instance of the CheckOut class as a
constructor parameter. This class would expose a writable copy of the data
(possibly through const_cast voodoo to avoid copies, if that's important)
and would actually perform the check-in as a part of it's destructor.

As far as exception safety - only you know when it's safe to allow a checkin
to occur. Add a 'freeze' function to the CheckIn class which modifies the
object's state to indicate that 1) changes are no longer allowed and 2) the
destructor should perform a check-in. A much harder problem is what happens
if an exception occurs during check-in. You'll either need guaranteed
no-throw code for check-in, or a process which provides the ACID transaction
guarantees to handle that.

-cd

Nicola Musatti

unread,
Oct 11, 2001, 11:42:20 AM10/11/01
to

"Mary K. Kuhner" wrote:
[...]


> One poster suggested using RAII for this, encapsulating the
> task in an object which makes the copy in its constructor and
> does the copying back onto the original in its destructor. This
> sounds very neat and elegant, but in implementing it I have
> encountered two problems:
>
> (1) If an exception is thrown while the copy is checked
> out, the destructor will be called and will attempt to check
> it back in. This is not a problem for my current program,
> which tends to treat all exceptions as terminal, but it
> seems like a future problem, when I get around to trying to
> give the program recovery capability. A check-in of a
> partially modified, possibly broken structure sounds like
> a bad idea. But I don't see how I can stop the check-in.
> (Testing to see if an exception is active?)

This shouldn't be the case: if an exception is thrown during check out
the RAII constructor doesn't run to completion, so the corresponding
destructor is not called. For this to be true you just have to ensure
that checkout/checkin is performed in the main object and not in a
subobject.

> (2) How do I provide const access? Some parts of the program
> really need to see this stuff, but they do not need to
> change it. Giving them write access violates const correctness.
> But I can't see how to make an object that copies in its
> constructor and checks-in in its destructor *except* when
> it's const, in which case it only copies in its constructor.

One way to make this possible could be to only checkin when some change
has been performed: as a const object cannot be modified, no checkin can
take place.

[...]


> I'm sure lots of people have coded objects like this. Any
> advice on these problems? I'm almost ready to abandon
> RAII here and go back to object.CheckOut() and object.CheckIn().
> But I really like RAII's security against forgetting to
> call CheckIn().

To tell you the truth, I don't think I'd use RAII for this task either,
mostly because I don't think that checkout/checkin functionality belongs
in the object itself. Rather I'd use some manager object; in order to
automatize checkin you could combine the manager with some form of smart
pointer which kept track (e.g. by a reference count) of being in use by
some client. A count of one could be taken to mean that the user is done
with your vector and that changes may be integrated in the common base.
Your manager could choose when to perform the corresponding check.

Cheers,
Nicola Musatti

Michael S. Terrazas

unread,
Oct 11, 2001, 11:43:48 AM10/11/01
to
"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote in message
news:9q1vgm$2170$1...@nntp6.u.washington.edu...
[snip background]

> (1) If an exception is thrown while the copy is checked
> out, the destructor will be called and will attempt to check
> it back in. This is not a problem for my current program,
> which tends to treat all exceptions as terminal, but it
> seems like a future problem, when I get around to trying to
> give the program recovery capability. A check-in of a
> partially modified, possibly broken structure sounds like
> a bad idea. But I don't see how I can stop the check-in.
> (Testing to see if an exception is active?)

I like to use something similar to the ODMG C++ binding to solve
this: Add a member function such as markModified(); changes to
the object will only be committed upon destruction of the
transaction is complete. Another idea I use from the same
specification is the idea of a Transaction class. I have used
several variations, one which just allows all the persistent
objects within the transaction scope to register themselves
when they feel they're done another that is a template on a
class-by-class basis. The difference is that the template is
easier to use when the consistency is whithin a tightly
group object web that is easy to verify from the root object,
the normal class allows more flexibilty when on a case-by-case
basis some other code needs to run exception-free in order
to complete the transaction. In either case, Transaction has
3 functions: begin() - to mark the transaction start; commit() -
to mark all the registered objects as commitable so that their
destructors can write; and abort() - destroy the objects without
writing. If a Transaction object is destroyed, it is as if abort
were called.

> (2) How do I provide const access? Some parts of the program
> really need to see this stuff, but they do not need to
> change it. Giving them write access violates const correctness.
> But I can't see how to make an object that copies in its
> constructor and checks-in in its destructor *except* when
> it's const, in which case it only copies in its constructor.

Using markModified() solves this problem. It is a non-const
function and since the write will only occur if it was called,
it can't happen on a const object.

It's implementation is a simple bool member which is not part
of the persistent state and initialized to false. It can be an
inline function that just sets the member to true.

Hope this helps,

Francis Glassborow

unread,
Oct 11, 2001, 12:56:12 PM10/11/01
to
In article <9q1vgm$2170$1...@nntp6.u.washington.edu>, Mary K. Kuhner
<mkku...@kingman.genetics.washington.edu> writes

>(1) If an exception is thrown while the copy is checked
>out, the destructor will be called and will attempt to check
>it back in. This is not a problem for my current program,
>which tends to treat all exceptions as terminal, but it
>seems like a future problem, when I get around to trying to
>give the program recovery capability. A check-in of a
>partially modified, possibly broken structure sounds like
>a bad idea. But I don't see how I can stop the check-in.
>(Testing to see if an exception is active?)

This is exactly what uncaught_exception is designed to do. Provide to
member functions, called, shall we say, commit and rollback. Now the
body of your dtor looks something like this:

if(uncaught_exception()) rollback();
else commit();

Now, if we you overload you ctor's for constness and add a variable:

bool nocommit;

you can further enhance your dtor with:

if(nocommit || uncaught_exception()) rollback()
else commit()


Francis Glassborow
I offer my sympathy and prayers to all those who are suffering
as a result of the events of September 11 2001.

Anthony Williams

unread,
Oct 11, 2001, 1:28:14 PM10/11/01
to
"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote in message
news:9q1vgm$2170$1...@nntp6.u.washington.edu...
> I recently asked for help with a design problem, and have settled
> on a solution that involves checking out a copy of something,
> modifying it, and checking it back in (transferring the changes
> to the original).
>
> One poster suggested using RAII for this, encapsulating the
> task in an object which makes the copy in its constructor and
> does the copying back onto the original in its destructor.

That would be me.

>This
> sounds very neat and elegant, but in implementing it I have
> encountered two problems:
>
> (1) If an exception is thrown while the copy is checked
> out, the destructor will be called and will attempt to check
> it back in. This is not a problem for my current program,
> which tends to treat all exceptions as terminal, but it
> seems like a future problem, when I get around to trying to
> give the program recovery capability. A check-in of a
> partially modified, possibly broken structure sounds like
> a bad idea. But I don't see how I can stop the check-in.
> (Testing to see if an exception is active?)

Option 1: Check std::uncaught_exception() in the destructor, and only check
in if false. Doesn't always work in practise, due to defective compilers.

Option 2: Have a flag in the checkin/checkout object, and only checkin if
set true. However, this defeats the point of using RAII.

Option 3: Option 2 in reverse - have a "dismiss" flag which prevents
checkin. Set the flag on error. This relies on setting the error flag
consistently. You could have

void func()
{
CheckOutObject obj;
try
{
// ...
}
catch(...)
{
obj.dismiss();
throw;
}
}

but that is a pain if it is done in many places.

Option 4: Implement your own version of uncaught_exception - only throw
user-defined exception classes, and make every exception object increase a
count in its constructors, and decrease it in its destructor. If the flag is
non-zero, there is an exception object in existence. This flag would still
be set in catch handlers (unlike std::uncaught_exception), but you are
unlikely to destroy an object in a catch handler that wasn't created in it,
so you could cache the value on creation and check against the cached value
(rather than zero) in the destructor.

> (2) How do I provide const access? Some parts of the program
> really need to see this stuff, but they do not need to
> change it. Giving them write access violates const correctness.
> But I can't see how to make an object that copies in its
> constructor and checks-in in its destructor *except* when
> it's const, in which case it only copies in its constructor.
>
> I could obviously write a second interface that allows const
> access, but there's a lot of redundant code involved.
> Inheritance of implementation looms as a possibility, but
> I don't really care for it.
>
> I'm sure lots of people have coded objects like this. Any
> advice on these problems? I'm almost ready to abandon
> RAII here and go back to object.CheckOut() and object.CheckIn().
> But I really like RAII's security against forgetting to
> call CheckIn().

You could go for the multiple reader/single writer locking strategy. Have
one Manager class that does a checkout, and a checkin if a flag is set, and
handles locking to ensure there is only one instance active at a time. This
class should only be constructible by one of two others - a ReaderLock
class, and a WriterLock class.

The ReaderLock class checks for existence of the Manager instance, and if
not present, creates one in "reader" mode. If it is present, and it is not
in "writer" mode, increase a reference count, otherwise block until free or
throw.

The WriterLock class checks for existence of the Manager instance, and if
not present, creates one in "writer" mode. If it is present, block until
free, or throw.

If the Manager class was created in "writer" mode, then it can only support
one reference, and does a checkin on destruction. In "reader" mode, it can
support multiple references, and should not do a checkin on destruction.

Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer

Steven E. Harris

unread,
Oct 11, 2001, 1:37:19 PM10/11/01
to

> A check-in of a partially modified, possibly broken structure sounds
> like a bad idea. But I don't see how I can stop the check-in.

I've used a boolean inside the RAII handle, initially set to false,
that governs whether to commit in the destructor. I provide a member
function called something like set_to_commit() that toggles that
boolean latch to true. That is, when you know the work succeeded, you
call set_to_commit() to stamp it done. The destructor will then
perform the commit.

The strange naming of the set_to_commit() member function tries to
acknowledge that we're not actually doing the commit at that point. It
certainly seems natural for the commit to happen upon confirmation of
successful work, so clearly it's not an ideal solution.

The interface is easier to understand in reverse - with the RAII
"guard" idiom. That is, "Clean up later unless I dismiss you." When
the user calls the intermediate latching function, he's *disabling*
work in the destructor. Conversely, with the set_to_commit() function
above, he's *enabling* work in the destructor. That raises the
question, "Why not just do that work now?"

Maybe we could recast the check-out/check-in problem with a "rollback"
guard. The constructor enables a transaction, and that transaction is
is rolled back in the destructor unless the guard is dismissed. Again,
one must remember to call dismiss() upon successful completion of
work, but the interface is easier to understand.

[...]

> I'm almost ready to abandon RAII here and go back to
> object.CheckOut() and object.CheckIn(). But I really like RAII's
> security against forgetting to call CheckIn().

In my scheme above, one can also forget to call set_to_commit(). We're
talking about something more than RAII, so RAII is too strict;
construction and destruction don't accommodate the range of outcomes
in this problem.

--
Steven E. Harris :: seha...@raytheon.com
Raytheon :: http://www.raytheon.com

Mike Schilling

unread,
Oct 11, 2001, 1:40:37 PM10/11/01
to
"Mary K. Kuhner" wrote:
>
> I recently asked for help with a design problem, and have settled
> on a solution that involves checking out a copy of something,
> modifying it, and checking it back in (transferring the changes
> to the original).
>
> One poster suggested using RAII for this, encapsulating the
> task in an object which makes the copy in its constructor and
> does the copying back onto the original in its destructor. This
> sounds very neat and elegant, but in implementing it I have
> encountered two problems:
>
> (1) If an exception is thrown while the copy is checked
> out, the destructor will be called and will attempt to check
> it back in. This is not a problem for my current program,
> which tends to treat all exceptions as terminal, but it
> seems like a future problem, when I get around to trying to
> give the program recovery capability. A check-in of a
> partially modified, possibly broken structure sounds like
> a bad idea. But I don't see how I can stop the check-in.
> (Testing to see if an exception is active?)


Create a state diagram for the object being checked out. For examples,
the states may look something like:

1. Not checked out
2. Checked out
3. Checked out and modified
4. Modifications complete

Ensure that the object's state is always updated correctly. (You can do
some consistency checking here, for instance, modifying an object in
state 1 in not allowed.) Now your checkin destructor can do the right
thing based on that state, for instance:

1. Nothing to do
2. Undo the checkout
3. Undo the checkout and write a log message that changes have been lost
4. Check the object in

The destructor will always return the object to state 1 (unless you
allow nested checkouts, of course.)

Herb Sutter

unread,
Oct 11, 2001, 4:36:40 PM10/11/01
to

>In article <9q1vgm$2170$1...@nntp6.u.washington.edu>, Mary K. Kuhner
><mkku...@kingman.genetics.washington.edu> writes
>>(1) If an exception is thrown while the copy is checked
>>out, the destructor will be called and will attempt to check
>>it back in. This is not a problem for my current program,
>>which tends to treat all exceptions as terminal, but it
>>seems like a future problem, when I get around to trying to
>>give the program recovery capability. A check-in of a
>>partially modified, possibly broken structure sounds like
>>a bad idea. But I don't see how I can stop the check-in.
>>(Testing to see if an exception is active?)
>
>This is exactly what uncaught_exception is designed to do. Provide to
>member functions, called, shall we say, commit and rollback. Now the
>body of your dtor looks something like this:
>
>if(uncaught_exception()) rollback();
>else commit();

That's often recommended, even by experts, but I believe it's unsound. I
give reasons in more detail in GotW #47 (http://www.gotw.ca/gotw/047.htm),
an enhanced version of which appears as Item 19 in More Exceptional C++ due
out next month, but here's a quick excerpt:

To quote directly from the standard (15.5.3/1):

The function bool uncaught_exception() returns true after completing
evaluation of the object to be thrown until completing the
initialization of the exception-declaration in the matching handler
(_lib.uncaught_). This includes stack unwinding. If the exception is
rethrown (_except.throw_), uncaught_exception() returns true from
the point of rethrow until the rethrown exception is caught again.

As it turns out, this specification is deceptively close to being useful.


[... a more general version of the question is elided ...]


Note that none of this is materially different from the following:

// Variant: Another wrong solution
//
Transaction::~Transaction() {
if( uncaught_exception() ) {
RollBack();
}
}

Again, note that this doesn't do the right thing if a transaction is
attempted in a destructor that might be called during stack unwinding:

// Variant: Why the wrong solution is still wrong
//
U::~U() {
try {
Transaction t( /*...*/ );
// do work
} catch( ... ) {
// clean up
}
}

The issue is that t.~Transaction will fail to commit the work that can be
safely done, and that ~U wants to do. For a particularly perverse but still
illustrative example, what if ~U is logging into a database information
about whether an exception was active or not? The transaction is always
rolled back, and will never be written out no matter how hard ~U tries to
commit it. ~Transaction just balks and pouts.

If you think it's a toy example, just change the name and consider a
database-aware RAII smart pointer that owns an in-memory object fetched from
a database, and writes it back out when it's done:

// Example calling code that uses the RAII
{
DBAwareSharedPtr x = GetSomeDatabaseObject();

// lots of stuff that could throw

} // here ~DBAwareSharedPtr releases the object whether an
// exception happened or not; a poster-child use of RAII

The smart pointer's destructor might look like this:

DBAwareSharedPtr::~DBAwareSharedPtr() {
Transaction t( /*...*/ );
// write the owned object back to the database
delete ownedObject_;
}

Clearly the write-it-out-and-delete-it should happen identically whether
~DBAwareSharedPtr is called during stack unwinding or not, for its whole
RAII purpose is precisely that, to own and free the same resource the same
way in either case. If ~Transaction were written as suggested above, it
would fail to write out the object in the precise case for which the RAII
wrapper exists in the first place.

The point as I see it is: Whether there's an uncaught exception active and
whether ~Transaction should commit are orthogonal, independent questions.
The first does not imply the second.

That's how I see it, anyway.

Herb


---
Herb Sutter (http://www.gotw.ca)

Secretary, ISO WG21 / ANSI J16 (C++) standards committee
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)

* Check out THE C++ Seminar: http://www.gotw.ca/cpp_seminar

Herb Sutter

unread,
Oct 11, 2001, 4:37:35 PM10/11/01
to
>I'm sure lots of people have coded objects like this. Any
>advice on these problems? I'm almost ready to abandon
>RAII here and go back to object.CheckOut() and object.CheckIn().
>But I really like RAII's security against forgetting to
>call CheckIn().

How about this:

- The RAIICheckerOuter constructor checks out an object, and owns the
checked-out object.

- If you get to a point when you want to safely check it back in, call
RAIICheckerOuter's CheckIn() function.

- In the RAIICheckerOuter destructor, if the object hasn't been checked in,
we cancel the checkout.

Note that cancelling the checkout is important, else you've left it checked
out and the repository is left in a state you probably don't want. But if
it's okay to keep things checked out across runs of the program, then that's
all right, and the RAIICheckerOuter constructor simply needs to be able to
re-check out and already-checked out object.


>(2) How do I provide const access? Some parts of the program
>really need to see this stuff, but they do not need to
>change it. Giving them write access violates const correctness.
>But I can't see how to make an object that copies in its
>constructor and checks-in in its destructor *except* when
>it's const, in which case it only copies in its constructor.

I'm not sure I follow. Wouldn't RAIICheckerOuter's internally held object be
non-const (possibly mutable), while RAIICheckerOuter's operator-> and
operator* provide only const pointers and references to it?

Herb


---
Herb Sutter (http://www.gotw.ca)

Secretary, ISO WG21 / ANSI J16 (C++) standards committee
Contributing Editor, C/C++ Users Journal (http://www.cuj.com)

* Check out THE C++ Seminar: http://www.gotw.ca/cpp_seminar

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Roland Pibinger

unread,
Oct 11, 2001, 4:38:40 PM10/11/01
to
On 11 Oct 2001 08:13:44 -0400,

>I recently asked for help with a design problem, and have settled
>on a solution that involves checking out a copy of something,
>modifying it, and checking it back in (transferring the changes
>to the original).
>
>One poster suggested using RAII for this, encapsulating the
>task in an object which makes the copy in its constructor and
>does the copying back onto the original in its destructor. This
>sounds very neat and elegant, but in implementing it I have
>encountered two problems:
>
>(1) If an exception is thrown while the copy is checked
>out, the destructor will be called and will attempt to check
>it back in. This is not a problem for my current program,
>which tends to treat all exceptions as terminal, but it
>seems like a future problem, when I get around to trying to
>give the program recovery capability. A check-in of a
>partially modified, possibly broken structure sounds like
>a bad idea. But I don't see how I can stop the check-in.
>(Testing to see if an exception is active?)

You need a commit-flag that indicates whether the destructor should
perform a check in or not. Your problem is similar to the one
described by Alexandrescu && Marginean (see esp. Solution 2). Maybe
you can even use their "ScopeGuard" allthough it "focuses only on the
cleanup part".
http://www.cuj.com/experts/1812/alexandr.htm?topic=experts

>(2) How do I provide const access? Some parts of the program
>really need to see this stuff, but they do not need to
>change it. Giving them write access violates const correctness.
>But I can't see how to make an object that copies in its
>constructor and checks-in in its destructor *except* when
>it's const, in which case it only copies in its constructor.
>
>I could obviously write a second interface that allows const
>access, but there's a lot of redundant code involved.
>Inheritance of implementation looms as a possibility, but
>I don't really care for it.

Give this kind of users only const objects. They cannot call a
non-const member function (commit(), checkIn(), ...) on a const
object.

>I'm sure lots of people have coded objects like this. Any
>advice on these problems? I'm almost ready to abandon
>RAII here and go back to object.CheckOut() and object.CheckIn().
>But I really like RAII's security against forgetting to
>call CheckIn().

Abandon RAII? It is the best C++ idiom available! (The poor Java
programmers must live without it).

Best wishes,
Roland Pibinger

Francis Glassborow

unread,
Oct 11, 2001, 6:03:27 PM10/11/01
to
In article <hqjbst4n1nh0jrk1j...@4ax.com>, Herb Sutter
<hsu...@acm.org> writes

> Note that none of this is materially different from the following:
>
> // Variant: Another wrong solution
> //
> Transaction::~Transaction() {
> if( uncaught_exception() ) {
> RollBack();
> }
> }
>
> Again, note that this doesn't do the right thing if a transaction is
> attempted in a destructor that might be called during stack unwinding:
>
> // Variant: Why the wrong solution is still wrong
> //
> U::~U() {
> try {
> Transaction t( /*...*/ );
> // do work
> } catch( ... ) {
> // clean up
> }
> }
>
>The issue is that t.~Transaction will fail to commit the work that can be
>safely done, and that ~U wants to do. For a particularly perverse but still
>illustrative example, what if ~U is logging into a database information
>about whether an exception was active or not? The transaction is always
>rolled back, and will never be written out no matter how hard ~U tries to
>commit it. ~Transaction just balks and pouts.

The inappropriate use of an idiom is always wrong:) IMO the error is not
in the concept of commit or rollback semantics implemented via a dtor
but in the idea that such a type could be used in a dtor. dtor's are
very dicey functions (procedures) and we should always think carefully
about defining objects in dtors exactly because it raises exception
safety issues if nothing else.

The problem in you code is not the existence of a type with commit or
rollback semantics (implemented in its dtor) but declaring an object of
such a type in a dtor. Just as we do not throw from dtors, we do not use
these objects in dtors.

Or have I missed your point? I hope that is not the case because the
commit or rollback idiom is attractive and is, with the above caveat, an
apparently good solution. I wish Kevlin were reading this thread,
because it was he who taught it to me and I have a profound respect for
his programming skills about equal with my respect for yours :) Perhaps
I should get the two of you to discuss this at the ACCU conference this
coming April (2-6)

Francis Glassborow
I offer my sympathy and prayers to all those who are suffering
as a result of the events of September 11 2001.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Sergey P. Derevyago

unread,
Oct 12, 2001, 3:06:25 PM10/12/01
to
Herb Sutter wrote:
> >if(uncaught_exception()) rollback();
> >else commit();
>
> That's often recommended, even by experts, but I believe it's unsound. I
> give reasons in more detail in GotW #47 (http://www.gotw.ca/gotw/047.htm),
> an enhanced version of which appears as Item 19 in More Exceptional C++ due
> out next month, but here's a quick excerpt:
>
> To quote directly from the standard (15.5.3/1):
>
> The function bool uncaught_exception() returns true after completing
> evaluation of the object to be thrown until completing the
> initialization of the exception-declaration in the matching handler
> (_lib.uncaught_). This includes stack unwinding. If the exception is
> rethrown (_except.throw_), uncaught_exception() returns true from
> the point of rethrow until the rethrown exception is caught again.
>
> As it turns out, this specification is deceptively close to being useful.
IMHO std::uncaught_exception is just another part of the language that was
not got right. It's seems like the following language extension can remedy
the issue:

class A {
// ...
~A(bool in_stack_unwinding) {
if (in_stack_unwinding) { /* ... */ }
else { /* ... */ }
}
};

And the legacy code will be unaffected because you don't have to define
destructor with the bool parameter if you don't want to deal with this
issue. I.e. you can define only one destructor: with or without the bool
parameter.
--
With all respect, Sergey. http://cpp3.virtualave.net/
mailto : ders at skeptik.net

Artem Livshits

unread,
Oct 12, 2001, 3:36:12 PM10/12/01
to
"Steven E. Harris" <seha...@raytheon.com> wrote in message
news:871ykam...@harris.sdo.us.ray.com...

> The interface is easier to understand in reverse - with the RAII
> "guard" idiom. That is, "Clean up later unless I dismiss you." When
> the user calls the intermediate latching function, he's *disabling*
> work in the destructor.

[...]

> Maybe we could recast the check-out/check-in problem with a "rollback"
> guard. The constructor enables a transaction, and that transaction is
> is rolled back in the destructor unless the guard is dismissed. Again,
> one must remember to call dismiss() upon successful completion of
> work, but the interface is easier to understand.

[...]

Actually, C++ has a built-in feature that can "rollback" steps that has
completed if some later steps fail to complete: object construction semantics.

E.g.

// void UndoStep1() throw();

class Step1
{
public:
Step1() { DoStep1(); }
~Step1() { UndoStep1(); }
};

// void UndoStep2() throw();

class Step2
{
public:
Step2() { DoStep2(); }
~Step2() { UndoStep2(); }
};

class A
{
Step1 step1_;
Step2 step2_;
};

void foo()
{
char dummy[sizeof(A)];
new (dummy) A();
}

foo() is atomic (provided that DoStep1 and DoStep2 are atomic) as if DoStep2()
fails the destructor of step1_ will call UndoStep1() so foo() either does both
steps or nothing.

Strictly speaking the last step doesn't need a destructor (unless A has it's
own constructor that may fail), but it eliminates the necessity to watch out
if a subobject is still the last step when the code is changed.


Artem Livshits
Brainbench MVP for C++
http://www.brainbench.com


--
Posted from tide85.microsoft.com [131.107.3.85]
via Mailgate.ORG Server - http://www.Mailgate.ORG

Hendrik Schober

unread,
Oct 22, 2001, 7:26:55 PM10/22/01
to
"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote:
> [...]

> (1) If an exception is thrown while the copy is checked
> out, the destructor will be called and will attempt to check
> it back in. [...]


As others have pointed out: Dtors are only called
for fully objects the ctor finished successfully.
You can use this to make the above work as you
need it.

> (2) How do I provide const access? Some parts of the program
> really need to see this stuff, but they do not need to
> change it. Giving them write access violates const correctness.
> But I can't see how to make an object that copies in its
> constructor and checks-in in its destructor *except* when
> it's const, in which case it only copies in its constructor.
>
> I could obviously write a second interface that allows const
> access, but there's a lot of redundant code involved.
> Inheritance of implementation looms as a possibility, but
> I don't really care for it.


Why not make a difference between Check-out/Check-in
and a simple read-only copy of the data?
That is, if a piece of code wants to modify the data,
it checks it out, gets a copy, modifies it, and then
submits it back.
If a piece of code needs a non-modifiable copy, it
simply gets a copy. Since Check-out/Check-in is to
be done by RAII, the latter case doesn't use RAII.
Instead, it (getting a copy of the data) is something
that's to be used by the RAII class.

> [...] I really like RAII's security against forgetting to
> call CheckIn().


If you're not always to check in, but differenciate
between success and failure, I don't see a way
around doing the decision by calling a (member)
function manually. (Except the already mentioned
'uncaught_exception()' idiom. But its usefullness is
still under debate.)
So, RAII helps you to not to forget to do _something_.
Although, what that would be, you have to decide.

> Mary Kuhner mkku...@eskimo.com


Schobi

--
Spam...@gmx.de is never read
I'm hschober at gmx dot de

Mary K. Kuhner

unread,
Oct 29, 2001, 3:38:57 PM10/29/01
to
In article <3bd49...@news.arcor-ip.de>,

Hendrik Schober <Spam...@gmx.de> wrote:
>"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote:
>> [...]
>> (1) If an exception is thrown while the copy is checked
>> out, the destructor will be called and will attempt to check
>> it back in. [...]

> As others have pointed out: Dtors are only called
> for fully objects the ctor finished successfully.
> You can use this to make the above work as you
> need it.

I'm not worried about an exception thrown from the constructer
of ParameterVector. I'm worried about an exception thrown by
the code that *holds* a ParameterVector and is in the process of
mangling it when the throw occurs.

{
ParameterVector paramvec; // obtain the check-out
Mangle(paramvec); // place the ParamVector in an inconsistent
state
UnMangle(paramvec); // get it consistent again
} // paramvec goes out of scope
here and is checked back in

This code is not happy with an exception thrown during Mangle or Unmangle
while the paramvec is inconsistent, because the check-in code in the
destructor will try to check the inconsistent mess back in.

Other posters have convinced me that RAII is not quite what I want here,
because I don't want the check-in to be completely automatic, specifically
because of this exception-safety issue.

Mary Kuhner mkku...@genetics.washington.edu

Albrecht Fritzsche

unread,
Oct 30, 2001, 10:52:06 AM10/30/01
to
Mary K. Kuhner wrote:


> {
> ParameterVector paramvec; // obtain the check-out
> Mangle(paramvec); // place the ParamVector in an
inconsistent
> state
> UnMangle(paramvec); // get it consistent again
> } // paramvec goes out of scope
> here and is checked back in
>

> Other posters have convinced me that RAII is not quite what I want here,
> because I don't want the check-in to be completely automatic, specifically
> because of this exception-safety issue.

Oh, why not? I would use a guard as a generalization of the RAII,
where in the constructor some kindof a memento of the paramvec is
created. The destructor would simply swap the messy paramvec by the
memento data, if no dismiss() function said otherwise, that no "roll-
back" should take place.

{
ParameterVector paramvec;
MementoGuard g(paramvec);
Mangle(paramvec);
UnMangle(paramvec);
g.dismiss();
}

Alexandrescu et al. have written a paper with some Guards which even
handle different forms of roll-backs dependent of the place where the
exception was thrown.

Ali
--
Albrecht Fritzsche, Software Developer
alfabet meta-modeling AG,
leibnizstr. 53, 10629 berlin, germany
http://www.alfabet.de

Hendrik Schober

unread,
Oct 30, 2001, 11:02:14 AM10/30/01
to
"Mary K. Kuhner" <mkku...@kingman.genetics.washington.edu> wrote:
> [...]
> I'm not worried about an exception thrown from the constructer
> of ParameterVector. I'm worried about an exception thrown by
> the code that *holds* a ParameterVector and is in the process of
> mangling it when the throw occurs.
>
> {
> ParameterVector paramvec; // obtain the check-out
> Mangle(paramvec); // place the ParamVector in an
inconsistent
> state
> UnMangle(paramvec); // get it consistent again
> } // paramvec goes out of scope
> here and is checked back in
> [...]

We have a transaction class here for DB stuff. It's
used like this:

{
Transaction ta(...);
do_something(...);
ta.commit();
}

If 'commit()' isn't called, 'ta' will roll back.

> Mary Kuhner mkku...@genetics.washington.edu

Schobi

--
Spam...@gmx.de is never read
I'm hschober at gmx dot de

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Christopher Eltschka

unread,
Dec 1, 2001, 4:31:49 AM12/1/01
to
Herb Sutter <hsu...@acm.org> writes:

IMHO it's quite easy to fix with a change which probably wouldn't
break many programs:

Instead returning a bool, return an int telling _how many_ exceptions
are pending.

Due to the implicit conversion from int to bool most programs wouldn't
even notice the change. Only if calling a function overloaded between
int and bool, instantiating templates using the return type, or
directly using the function signature (like assigning
uncaught_exception to a function pointer), a program would detect this
change. But I consider all of them unlikely for uncaught_excption
(except maybe passing it to output streams for debugging purposes; in
that case it may break diagosis tools if the program uses non-numeric
bool representation on output and the tool depends on it).

With the above change, you could write

class Foo
{
public:
Foo(): except(uncaught_exception()) {}
~Foo()
{
if (uncaught_exception() != except)
rollback();
else
commit();
}
// ...
private:
int except;
};

[...]

Sergey P. Derevyago

unread,
Dec 1, 2001, 4:33:24 PM12/1/01
to
Christopher Eltschka wrote:
> Instead returning a bool, return an int telling _how many_ exceptions
> are pending.
>
> Due to the implicit conversion from int to bool most programs wouldn't
> even notice the change. Only if calling a function overloaded between
> int and bool, instantiating templates using the return type, or
> directly using the function signature (like assigning
> uncaught_exception to a function pointer), a program would detect this
> change.
And also someone could use a pointer to uncaught_exception.

I propose to allow to add the bool parameter to destructors
(http://groups.google.com/groups?as_umsgid=3BC7214E.CDD9CF8E%40iobox.com&hl=en
):

class A {
// ...
~A(bool in_stack_unwinding) {
if (in_stack_unwinding) { /* ... */ }
else { /* ... */ }
}
};

IMHO it is exactly the Right Thing :)


--
With all respect, Sergey. http://cpp3.virtualave.net/
mailto : ders at skeptik.net

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

0 new messages