The "smart guarantee"?

D

David B. Held

I wanted to post this proposal on c.l.c++.m, but my news
server apparently does not support that group any more.

I propose a new class of exception safety known as the
"smart guarantee". Essentially, the smart guarantee
promises to clean up resources whose ownership is
passed into the function, for whatever defintion of "clean
up" is most appropriate for the resource passed.
Note that this is different from both the basic and the
strong guarantee.

According to David Abrahams, (here:
http://www.boost.org/more/generic_exception_safety.html)
the common guarantees are as follows:

The basic guarantee: that the invariants of the
component are preserved, and no resources
are leaked.
The strong guarantee: that the operation has either
completed successfully or thrown an exception,
leaving the program state exactly as it was before
the operation started.
The no-throw guarantee: that the operation will not
throw an exception.

Now, one could be pedantic and say that the strong
guarantee doesn't really promise that the program state
will be "exactly" as it was before the operation started,
since there is now an exception object present in the
state which was not present before (if an exception is
thrown, of course). However, let us not dwell on that
pedantry, but a different one: what if the "operation" is
a function which receives ownership of a resource,
like so:

void foo(obj* p);

foo(new obj);

Now, before the function is called, a new object is
created on the heap. However, the only reference
to that object is bound to a parameter of foo(). So
technically, foo() is free to allow the last (and only)
reference to obj go out of scope, and still provide
the strong guarantee. If an exception is thrown, foo()
exits, and the object is still on the heap; hence, the
program state is preserved. Thus, foo() can be
strongly exception safe and leak a resource.

Clearly, most implementors of foo() will try to properly
dispose of p in the event of an exception. But now
foo() doesn't offer the strong guarantee, because if
an exception is thrown, the exit state will be different
from the entry state. However, such an implementation
of foo() still offers some measure of safety in the face
of exceptions, and foo() may provide the strong
guarantee for all of the state not including the
resources whose ownership was transferred into the
function.

I propose to call this level of safety the "smart
guarantee". It may seem like an obscure corner
case which does not deserve a name of its own,
because functions which take sole ownership of a
resource are not so common. I argue that if the
language receives fundamental support for move
semantics, then such functions may, in fact, become
more common; and thus this level of exception
safety may become more relevant.

I chose the name "smart" because that seems to
connote an awareness of resources or some type
of automatic management. Note that the smart
guarantee is somewhat orthogonal to the basic and
strong guarantee. So a function could provide the
basic guarantee for local state, and the smart
guarantee for ownership-transfer arguments.
Or, it could offer the strong guarantee for all
ownership-stable state, and the smart guarantee
for ownership-transfer state. It is perhaps useful to
call these situations the "smart basic guarantee"
and the "smart strong guarantee", respectively.
I think of the "smart guarantee" as the "smart
strong guarantee" by default.

To explicitly state that an operation does not
provide the smart guarantee, but does provide
one of the other guarantees, I would say that it
provides the "simple basic" or "simple strong"
guarantee. By default, "basic guarantee" and
"strong guarantee" should mean the simple
versions.

Comments are welcome.

Dave
 
D

David B. Held

After some consideration, I realized that perhaps I
misunderstood the intended meaning of the basic
guarantee, and that most of what I suggest for the
smart guarantee is, in fact, covered by the basic
guarantee. However, I still maintain that there is a
middle ground between the basic and the strong
guarantee which should still be called the "smart
guarantee". But I no longer believe it is orthogonal
to the other guarantees. Rather, I believe it
occupies a point on the ladder of exception safety.

Consider a member or friend function which takes
ownership of an external resource:

class foo
{
// ...
public:
foo(obj* p)
{
try
{
some_init(p); // might throw
}
catch (...)
{
delete p;
}
}
void acquire(obj* p)
{
foo(p).swap(*this);
}
void swap(foo& f); // nothrow
};

Now, acquire() provides the basic guarantee,
because foo's invariants are preserved, and p
is not leaked if foo(p) throws. However, acquire()
provides *more* than the basic guarantee,
because more than the invariants are preserved.
In fact, the entire state of foo is preserved. And
yet, acquire() provides *less* than the strong
guarantee, because it does not preserve the entire
program state (because it deletes p).

I contend that this level of safety is useful to identify,
because it is analogous to the strong guarantee
while not being the strong guarantee. You know
that foo's state is preserved, even if the external
state is not, and that can be a useful piece of
information in analyzing foo's behaviour in the
presence of exceptions.

Dave
 
R

Rob Williscroft

David B. Held wrote in
I wanted to post this proposal on c.l.c++.m, but my news
server apparently does not support that group any more.

X-Post added.

http://groups.google.co.uk/groups?group=comp.lang.c++.moderated

Additionally here are a number of newsservers that will give you
readonly access I found allnews.readfreenews.net here:
http://freenews.maxbaud.net/newspage.html?date=2003-09-05
some apparently allow posting but I didn't check if any carry
c.l.c++.m.


After some consideration, I realized that perhaps I
misunderstood the intended meaning of the basic
guarantee, and that most of what I suggest for the
smart guarantee is, in fact, covered by the basic
guarantee. However, I still maintain that there is a
middle ground between the basic and the strong
guarantee which should still be called the "smart
guarantee". But I no longer believe it is orthogonal
to the other guarantees. Rather, I believe it
occupies a point on the ladder of exception safety.

Consider a member or friend function which takes
ownership of an external resource:

class foo
{
// ...
public:
foo(obj* p)
{
try
{
some_init(p); // might throw
}
catch (...)
{
delete p;
}
}
void acquire(obj* p)
{
foo(p).swap(*this);
}
void swap(foo& f); // nothrow
};

Now, acquire() provides the basic guarantee,
because foo's invariants are preserved, and p
is not leaked if foo(p) throws. However, acquire()
provides *more* than the basic guarantee,
because more than the invariants are preserved.
In fact, the entire state of foo is preserved.

Couldn't this be expressed as the the basic gaurantee +
entire state is invariant?
And
yet, acquire() provides *less* than the strong
guarantee, because it does not preserve the entire
program state (because it deletes p).

I contend that this level of safety is useful to identify,
because it is analogous to the strong guarantee
while not being the strong guarantee. You know
that foo's state is preserved, even if the external
state is not, and that can be a useful piece of
information in analyzing foo's behaviour in the
presence of exceptions.

What you need here is an example of a client of this
new guarantee, the problem I see is that the service is
offering to preserve its own state but not the clients.
As a client I don't think I'd have any use for that.

Rob.
 
D

David Abrahams

David B. Held said:
Consider a member or friend function which takes
ownership of an external resource:

class foo
{
// ...
public:
foo(obj* p)
{
try
{
some_init(p); // might throw
}
catch (...)
{
delete p;
}
}
void acquire(obj* p)
{
foo(p).swap(*this);
}
void swap(foo& f); // nothrow
};

Now, acquire() provides the basic guarantee,
because foo's invariants are preserved, and p
is not leaked if foo(p) throws. However, acquire()
provides *more* than the basic guarantee,
because more than the invariants are preserved.
In fact, the entire state of foo is preserved. And
yet, acquire() provides *less* than the strong
guarantee, because it does not preserve the entire
program state (because it deletes p).

I contend that this level of safety is useful to identify,

That is really the key question. There's not actually a "ladder" of
exception safety distinctions: it's a lattice. For example, you could
imagine a similar guarantee which says that *this might change if an
exception is thrown, but none of the arguments will be modified (in
addition to the basic guarantee of course). There are an infinite
number of other such distinctions.

But you've put your finger on it: is your guarantee really useful? I
chose to name the particular distinctions I did because they *were*
useful for reasoning about program correctness. How would you use
your guarantee in program design?
because it is analogous to the strong guarantee
while not being the strong guarantee.

That, in itself, does *not* make it useful. The guarantee I invented
above can make the same claims about being similar to the strong
guarantee yet I've never heard of anyone using or wanting it. I
could come up with any number of others.

In fact, why should *this be special? It's an argument like all the
others, except that it's "hidden".
You know that foo's state is preserved, even if the external state
is not, and that can be a useful piece of information in analyzing
foo's behaviour in the presence of exceptions.

Anything *could* be useful, but the proof is in the pudding.
Distinctions are useful in proportion to their starkness. If we
labelled every point in the spectrum we would have a wide array of
terms, but I claim it would leave us less powerful to identify program
behaviors, not more.
 
D

David B. Held

David Abrahams said:
[...]
That is really the key question. There's not actually a
"ladder" of exception safety distinctions: it's a lattice.

The set of all possible exception safety rules is a lattice,
but the "known" rules form a non-decreasing hierarchy
of state integrity. The basic guarantee only promises that
in the event of an exception, state will be valid. The smart
guarantee says that state will be known, and most of it
will be unchanged. The strong and nothrow guarantee
say that all state will be unchanged.
For example, you could imagine a similar guarantee
which says that *this might change if an exception is
thrown, but none of the arguments will be modified
(in addition to the basic guarantee of course).

Yes, but can you cite one non-contrived instance in which
this guarantee offers something useful?
There are an infinite number of other such distinctions.

And most of the others are not useful, as you say below.
But you've put your finger on it: is your guarantee really
useful? I chose to name the particular distinctions I did
because they *were* useful for reasoning about
program correctness. How would you use your
guarantee in program design?

Let's use your example, but tweak it a bit. Instead of using
std::set, let's say we have a set container that uses pointer semantics, and
that it takes ownership of the objects put
into it. Furthermore, ptr_set::insert() gives the smart
guarantee:

template <class T>
class SearchableStack
{
public:
void push(T* t); // O(log n)
void pop(); // O(log n)
bool contains(T* t) const; // O(log n)
T* top() const; // O(1)
private:
ptr_set<T> set_impl;
std::list<ptr_setset<T>::iterator> list_impl;
};

/* 01 */ template <class T>
/* 02 */ void SearchableStack<T>::push(T* t)
/* 03 */ {
/* 04 */ ptr_set<T>::iterator i = set_impl.insert(t);
/* 05 */ try
/* 06 */ {
/* 07 */ list_impl.push_back(i);
/* 08 */ }
/* 09 */ catch(...)
/* 10 */ {
/* 11 */ set_impl.erase(i);
/* 12 */ throw;
/* 13 */ }
/* 14 */ }

The analysis would be the same as with your example
except for line 4. Not only do we want ptr_set::insert()
to not change the set if it fails, we also want it to clean
up t as well, so we can write code like so:

That, in itself, does *not* make it useful. The guarantee
I invented above can make the same claims about
being similar to the strong guarantee yet I've never
heard of anyone using or wanting it. I could come up
with any number of others.

Yes, but your example is not similar to the strong
guarantee in a useful way. My point is that the smart
guarantee is similar to the strong guarantee for all of
the state that you *wish* to be preserved, and only
violates transaction semantics to fulfill the constraint
that no resources are leaked. Since one does not need
to modify *this to prevent resource leakage, such a
property would not seem desirable.
In fact, why should *this be special? It's an argument
like all the others, except that it's "hidden".

I don't think *this is particularly special. What *is* special
is resources being bound to a function parameter with
no other references. That is the case that merits special
attention, IMO.
[...]
Anything *could* be useful, but the proof is in the
pudding. Distinctions are useful in proportion to their
starkness. If we labelled every point in the spectrum
we would have a wide array of terms, but I claim it
would leave us less powerful to identify program
behaviors, not more.

True enough. And like I said before, ownership transfer
may be rare enough that this distinction is not that
useful. I can't list a bunch of use cases where the
smart guarantee is an important part of exception safety
analysis. But I think it does address what seems to be
a flaw, or at least, a peculiarity, of the strong guarantee,
without inventing new exception guidelines arbitrarily.

Dave
 
A

Alexander Terekhov

:
[...]
Now, one could be pedantic and say that the strong
guarantee doesn't really promise that the program state
will be "exactly" as it was before the operation started,
since there is now an exception object present in the
state which was not present before (if an exception is
thrown, of course). However, let us not dwell on that
pedantry, but a different one: what if the "operation" is
a function which receives ownership of a resource,
like so:

void foo(obj* p);

then the "smart way" to tell the world about such incredible
peculiarity is nothing but just-do-it-like-so:

void foo(std::auto_ptr<obj> p);

Oder?

regards,
alexander.
 
D

David B. Held

Rob Williscroft said:
[...]
X-Post added.
Thanks.

[...]
Couldn't this be expressed as the the basic gaurantee +
entire state is invariant?

Yes, but I think that it occurs often enough that it deserves
its own name.
[...]
What you need here is an example of a client of this
new guarantee, the problem I see is that the service is
offering to preserve its own state but not the clients.
As a client I don't think I'd have any use for that.

Here is the motivating example:

void bar()
{
some_smart_ptr p(new obj);
// do stuff
}

Have you ever written code like this before? In this case,
you almost certainly don't want some_smart_ptr to
preserve your state, because you are giving away your
only reference to obj. Here is a perfect example of the
strong guarantee being "more safe" than you really
want. It should be obvious that you really do want the
function (in this case, and probably most, a c'tor) to
modify your state (by deleting your object) in the case of
an exception, but you also want it to provide the strong
guarantee for the rest of the state. So the guarantee
*does* preserve the client's state *whose ownership
is not transferred into the function*. The only state that
the guarantee says it will modify is exactly that state
which you would want it to modify anyway, which is
ownership-transferred resources. Otherwise, it is the
same as the strong guarantee.

For example, if you had this instead:

void baz()
{
a_deleter d;
some_smart_ptr p(new obj, d);
// do other stuff
}

the smart guarantee would say that d is unchanged in
the event of an exception, even if a non-const & to d is
passed in (for whatever reason). That's because
ownership of d is not being transferred into the function.

Dave
 
D

David B. Held

Alexander Terekhov said:
[...]
then the "smart way" to tell the world about such
incredible peculiarity is nothing but just-do-it-like-so:

void foo(std::auto_ptr<obj> p);

Ah, but what if foo() is none other than
auto_ptr::auto_ptr(T* p), or some other resource wrapper
c'tor? And if it's so peculiar, why do we have auto_ptr?

Dave
 
A

Alexander Terekhov

David B. Held said:
Alexander Terekhov said:
[...]
then the "smart way" to tell the world about such
incredible peculiarity is nothing but just-do-it-like-so:

void foo(std::auto_ptr<obj> p);

Ah, but what if foo() is none other than
auto_ptr::auto_ptr(T* p),

That thing is throw()-nothing.
or some other resource wrapper c'tor?
http://groups.google.com/[email protected]

And if it's so peculiar, why do we have auto_ptr?

Irony aside, teleportation, you know.

http://www.research.ibm.com/quantuminfo/teleportation

regards,
alexander.
 
R

Rob Williscroft

David B. Held wrote in
Yes, but I think that it occurs often enough that it deserves
its own name.

I think it already has (kind of), see below.

[snip]
Here is the motivating example:

void bar()
{
some_smart_ptr p(new obj);
// do stuff
}

Have you ever written code like this before? In this case,
you almost certainly don't want some_smart_ptr to
preserve your state, because you are giving away your
only reference to obj. Here is a perfect example of the
strong guarantee being "more safe" than you really
want. It should be obvious that you really do want the
function (in this case, and probably most, a c'tor) to
modify your state (by deleting your object) in the case of
an exception, but you also want it to provide the strong
guarantee for the rest of the state.

Its the bit were you say "(by deleting your object)", Where I just
don't see it that way, once the paramiter has been succesfully
passed it belong's to the state of the function or ctor.
So the guarantee
*does* preserve the client's state *whose ownership
is not transferred into the function*. The only state that
the guarantee says it will modify is exactly that state
which you would want it to modify anyway, which is
ownership-transferred resources. Otherwise, it is the
same as the strong guarantee.

For example, if you had this instead:

void baz()
{
a_deleter d;
some_smart_ptr p(new obj, d);
// do other stuff
}

the smart guarantee would say that d is unchanged in
the event of an exception, even if a non-const & to d is
passed in (for whatever reason). That's because
ownership of d is not being transferred into the function.

This seems to be about RAII intialization and who takes
responsibility for the (or any) exception guarantees eg:

void client_responsible()
{
std::auto_ptr< obj > p( new obj );
server( p.get() );
p.release();
}

server_responsable would be your example and then there's:

void its_a_contract()
{
std::auto_ptr< obj > temp( new obj )
server( temp );
}

Note that both client_reponsible() and its_a_contract() can be
adapted to a server() taking multiple resources but:

void wont_work()
{
server( new obj, new obj );
}

can't. If either of the new obj expressions throw's the the other
leaks.

Which all means that whatever function or ctor we are talking about
that offers the Smart Guarantee is also a resource initializer,
wether we call it that or not.

From: http://www.boost.org/more/generic_exception_safety.html

<quote>
The basic guarantee: that the invariants of the component are
preserved, and no resources are leaked.
</quote>

I think that covers a RAII initializer. Though perhaps if it isn't
obvious (i.e. smart_ptr<> is obviously RAII) that RAII symantics
apply, we should document that the function/ctor is a RAII
initializer.

Rob.
 
D

David B. Held

Rob Williscroft said:
[...]
Its the bit were you say "(by deleting your object)", Where
I just don't see it that way, once the paramiter has been
succesfully passed it belong's to the state of the function
or ctor.

Well, David Abrahams doesn't see it that way, and he
wrote the exception safety definitions you quote below.
[...]
Note that both client_reponsible() and its_a_contract()
can be adapted to a server() taking multiple resources

Just like you can build your own strong guarantee, but
it's still nice when a component provides it for you. In
some extreme cases, you can't build your own strong
guarantee (such as for non-copyable objects), but it
seems unlikely that there are any cases where you
couldn't build your own "smart guarantee".
but:

void wont_work()
{
server( new obj, new obj );
}

can't. If either of the new obj expressions throw's the the
other leaks.

Which is why this is a bad thing to do in general, and I've
never encountered a situation where I even *wanted* to
do this.
Which all means that whatever function or ctor we are
talking about that offers the Smart Guarantee is also a
resource initializer, wether we call it that or not.

I'm not sure what you mean by "resource initializer", but
consider a function like some_smart_pointer::reset(T* p).
Is that still "resource initialization"?

If you notice, I quoted this in my original post. You could
have just quoted my quote. ;)
<quote>
The basic guarantee: that the invariants of the component are
preserved, and no resources are leaked.
</quote>

I think that covers a RAII initializer. Though perhaps if it
isn't obvious (i.e. smart_ptr<> is obviously RAII) that RAII
symantics apply, we should document that the function/ctor
is a RAII initializer.

The basic guarantee is a necessary but not sufficient
condition of the smart guarantee. The point of the smart
guarantee is to go beyond preserving invariants, and say
that just about all of the state is actually preserved. After
all, preserving invariants isn't nearly as useful as
preserving state.

Dave
 
D

David Abrahams

David B. Held said:
David Abrahams said:
[...]
That is really the key question. There's not actually a
"ladder" of exception safety distinctions: it's a lattice.

The set of all possible exception safety rules is a lattice,
but the "known" rules form a non-decreasing hierarchy
of state integrity. The basic guarantee only promises that
in the event of an exception, state will be valid. The smart
guarantee says that state will be known, and most of it
will be unchanged.

That isn't the way you characterized it before. Maybe you need to
nail down the definition.
The strong and nothrow guarantee say that all state will be unchanged.

Actually the nothrow guarantee says there will be no exception, so you
can draw any conclusion you like about what would happen if an
exception were to occur ;-)
Yes, but can you cite one non-contrived instance in which
this guarantee offers something useful?

Not offhand. I think that's my point; I'm offering you the same
challenge.
Let's use your example, but tweak it a bit. Instead of using
std::set, let's say we have a set container that uses pointer semantics, and
that it takes ownership of the objects put
into it. Furthermore, ptr_set::insert() gives the smart
guarantee:

template <class T>
class SearchableStack
{
public:
void push(T* t); // O(log n)
void pop(); // O(log n)
bool contains(T* t) const; // O(log n)
T* top() const; // O(1)
private:
ptr_set<T> set_impl;
std::list<ptr_setset<T>::iterator> list_impl;
};

/* 01 */ template <class T>
/* 02 */ void SearchableStack<T>::push(T* t)
/* 03 */ {
/* 04 */ ptr_set<T>::iterator i = set_impl.insert(t);
/* 05 */ try
/* 06 */ {
/* 07 */ list_impl.push_back(i);
/* 08 */ }
/* 09 */ catch(...)
/* 10 */ {
/* 11 */ set_impl.erase(i);
/* 12 */ throw;
/* 13 */ }
/* 14 */ }

The analysis would be the same as with your example
except for line 4. Not only do we want ptr_set::insert()
to not change the set if it fails, we also want it to clean
up t as well, so we can write code like so:

SearchableStack<obj> s;
s.push(new obj);

Ah, OK. But then if you want this guarantee to be useful, *this can't
be special at all; there might be other persistent arguments which you
want to say haven't changed either. I think you just want to have a
way of labelling certain program state as changeable in case of an
exception. For this to be useful, you need to be explicit about which
state is changeable; it isn't enough to say "it offers the ``smart
guarantee''".

By the way, I think "smart guarantee" is a terrible name for this
idea. There's nothing dumb about it, but also nothing particularly
smart about it (as compared with the other guarantees).
Yes, but your example is not similar to the strong
guarantee in a useful way. My point is that the smart
guarantee is similar to the strong guarantee for all of
the state that you *wish* to be preserved, and only
violates transaction semantics to fulfill the constraint
that no resources are leaked. Since one does not need
to modify *this to prevent resource leakage, such a
property would not seem desirable.


I don't think *this is particularly special. What *is* special
is resources being bound to a function parameter with
no other references. That is the case that merits special
attention, IMO.

But that's a feature of the caller, not of the callee!

SearchableStack s;
T* p = new T;
s.push(p);
p->whatever();

If you want to make it into a feature of the callee, pass a
std::auto_ptr by value. Then you know that nobody outside the caller
can legally use the pointer after the call, because the interface
enforces it. Since function arguments never survive a call and the
resource is wholly owned by the auto_ptr you can just say the function
gives the strong guarantee; end-of-story.
[...]
Anything *could* be useful, but the proof is in the
pudding. Distinctions are useful in proportion to their
starkness. If we labelled every point in the spectrum
we would have a wide array of terms, but I claim it
would leave us less powerful to identify program
behaviors, not more.

True enough. And like I said before, ownership transfer
may be rare enough that this distinction is not that
useful. I can't list a bunch of use cases where the
smart guarantee is an important part of exception safety
analysis.

That's my point.
But I think it does address what seems to be
a flaw, or at least, a peculiarity, of the strong guarantee,
without inventing new exception guidelines arbitrarily.

It's neither a flaw nor a peculiarity of the strong guarantee, IMO.
It covers the case perfectly when you use std::auto_ptr, and isn't
designed to cover the case otherwise. I don't believe the few authors
of smart pointers need a whole new distinction named for what they do
in their constructors in order to make the theory complete -- they can
just spell out the semantics directly. Furthermore, in an ideal world
(when we get template varargs or at least the forwarding problem
solved) nobody will write new-expressions directly anyway. Instead
we'll use factory functions like:

std::auto_ptr<T>::new_(arg1, arg2, arg3)

which are equivalent to:

std::auto_ptr<T>(new T(arg1,arg2,arg3))

My 33 cents,
Dave
 
D

David B. Held

David Abrahams said:
[...]
That isn't the way you characterized it before.

I'm pretty sure it is, but I don't think it's worth the time to review.
[Me]
Yes, but can you cite one non-contrived instance in which
this guarantee offers something useful?

Not offhand. I think that's my point; I'm offering you the
same challenge.

I offered some examples. I suppose you think containers
with pointer semantics are "contrived", though, huh?
[...]
Ah, OK. But then if you want this guarantee to be useful,
*this can't be special at all;

I'm not sure where you got the idea that I said *this should
be handled specially.
there might be other persistent arguments which you
want to say haven't changed either. I think you just want
to have a way of labelling certain program state as
changeable in case of an exception.
Yes.

For this to be useful, you need to be explicit about which
state is changeable; it isn't enough to say "it offers the
``smart guarantee''".

Actually, I *was* explicit about which state is changeable.
The state that is changeable is dynamic resources whose
ownership is irreversibly transferred into the function. That
turns out to be the only state which you would *want* to
change in order to have a good safety guarantee.
By the way, I think "smart guarantee" is a terrible name
for this idea. There's nothing dumb about it, but also
nothing particularly smart about it (as compared with the
other guarantees).

Actually, there is. It promises to give you the strong
guarantee except that it will do one smart thing and
automatically clean up resources. In the sense that "smart"
is used in other contexts to imply automatic management,
especially of resources, I think it is quite reasonable.
[...]
SearchableStack s;
T* p = new T;
s.push(p);
p->whatever();

If you want to make it into a feature of the callee, pass a
std::auto_ptr by value.

But if s were an auto_ptr<>, what would you do?

T* p = new T;
auto_ptr<T> s(p);

Why allow the inline new call in one context, but not another?
Then you know that nobody outside the caller can legally
use the pointer after the call, because the interface
enforces it. Since function arguments never survive a call
and the resource is wholly owned by the auto_ptr you can
just say the function gives the strong guarantee; end-of-
story.

Yes. But this is a case of externally giving a stronger
guarantee, which is just like externally giving a function
the strong guarantee by copying the modifiable state first.
So then your argument is that no component should give
the strong guarantee because a client can always provide
it herself? If a function can provide the strong guarantee
efficiently, isn't it better if it does so?
[...]
But I think it does address what seems to be
a flaw, or at least, a peculiarity, of the strong
guarantee, without inventing new exception
guidelines arbitrarily.

It's neither a flaw nor a peculiarity of the strong
guarantee, IMO. It covers the case perfectly when you
use std::auto_ptr, and isn't designed to cover the case
otherwise.

But then, we don't need to have the strong guarantee,
since we can almost always provide it ourselves.
I don't believe the few authors of smart pointers need a
whole new distinction named for what they do in their
constructors

Don't forget reset(). ;>
in order to make the theory complete -- they can just
spell out the semantics directly.

Ok, you caught me. ;) I was hoping that containers with
pointer semantics would provide another justification,
but they probably wouldn't add considerably to the
number of users who want the smart guarantee.
Furthermore, in an ideal world (when we get template
varargs or at least the forwarding problem solved)
nobody will write new-expressions directly anyway.
Instead we'll use factory functions like:

std::auto_ptr<T>::new_(arg1, arg2, arg3)

which are equivalent to:

std::auto_ptr<T>(new T(arg1,arg2,arg3))

That would certainly be nice.

Dave
 
D

David Abrahams

David Abrahams said:
[...]
That is really the key question. There's not actually a
"ladder" of exception safety distinctions: it's a lattice.

The set of all possible exception safety rules is a lattice,
but the "known" rules form a non-decreasing hierarchy
of state integrity. The basic guarantee only promises that
in the event of an exception, state will be valid. The smart
guarantee says that state will be known, and most of it
will be unchanged.

That isn't the way you characterized it before. Maybe you need to
nail down the definition.
The strong and nothrow guarantee say that all state will be unchanged.

Actually the nothrow guarantee says there will be no exception, so you
can draw any conclusion you like about what would happen if an
exception were to occur ;-)
Yes, but can you cite one non-contrived instance in which
this guarantee offers something useful?

Not offhand. I think that's my point; I'm offering you the same
challenge.
Let's use your example, but tweak it a bit. Instead of using
std::set, let's say we have a set container that uses pointer semantics, and
that it takes ownership of the objects put
into it. Furthermore, ptr_set::insert() gives the smart
guarantee:

template <class T>
class SearchableStack
{
public:
void push(T* t); // O(log n)
void pop(); // O(log n)
bool contains(T* t) const; // O(log n)
T* top() const; // O(1)
private:
ptr_set<T> set_impl;
std::list<ptr_setset<T>::iterator> list_impl;
};

/* 01 */ template <class T>
/* 02 */ void SearchableStack<T>::push(T* t)
/* 03 */ {
/* 04 */ ptr_set<T>::iterator i = set_impl.insert(t);
/* 05 */ try
/* 06 */ {
/* 07 */ list_impl.push_back(i);
/* 08 */ }
/* 09 */ catch(...)
/* 10 */ {
/* 11 */ set_impl.erase(i);
/* 12 */ throw;
/* 13 */ }
/* 14 */ }

The analysis would be the same as with your example
except for line 4. Not only do we want ptr_set::insert()
to not change the set if it fails, we also want it to clean
up t as well, so we can write code like so:

SearchableStack<obj> s;
s.push(new obj);

Ah, OK. But then if you want this guarantee to be useful, *this can't
be special at all; there might be other persistent arguments which you
want to say haven't changed either. I think you just want to have a
way of labelling certain program state as changeable in case of an
exception. For this to be useful, you need to be explicit about which
state is changeable; it isn't enough to say "it offers the ``smart
guarantee''".

By the way, I think "smart guarantee" is a terrible name for this
idea. There's nothing dumb about it, but also nothing particularly
smart about it (as compared with the other guarantees).
Yes, but your example is not similar to the strong
guarantee in a useful way. My point is that the smart
guarantee is similar to the strong guarantee for all of
the state that you *wish* to be preserved, and only
violates transaction semantics to fulfill the constraint
that no resources are leaked. Since one does not need
to modify *this to prevent resource leakage, such a
property would not seem desirable.


I don't think *this is particularly special. What *is* special
is resources being bound to a function parameter with
no other references. That is the case that merits special
attention, IMO.

But that's a feature of the caller, not of the callee!

SearchableStack s;
T* p = new T;
s.push(p);
p->whatever();

If you want to make it into a feature of the callee, pass a
std::auto_ptr by value. Then you know that nobody outside the caller
can legally use the pointer after the call, because the interface
enforces it. Since function arguments never survive a call and the
resource is wholly owned by the auto_ptr you can just say the function
gives the strong guarantee; end-of-story.
[...]
Anything *could* be useful, but the proof is in the
pudding. Distinctions are useful in proportion to their
starkness. If we labelled every point in the spectrum
we would have a wide array of terms, but I claim it
would leave us less powerful to identify program
behaviors, not more.

True enough. And like I said before, ownership transfer
may be rare enough that this distinction is not that
useful. I can't list a bunch of use cases where the
smart guarantee is an important part of exception safety
analysis.

That's my point.
But I think it does address what seems to be
a flaw, or at least, a peculiarity, of the strong guarantee,
without inventing new exception guidelines arbitrarily.

It's neither a flaw nor a peculiarity of the strong guarantee, IMO.
It covers the case perfectly when you use std::auto_ptr, and isn't
designed to cover the case otherwise. I don't believe the few authors
of smart pointers need a whole new distinction named for what they do
in their constructors in order to make the theory complete -- they can
just spell out the semantics directly. Furthermore, in an ideal world
(when we get template varargs or at least the forwarding problem
solved) nobody will write new-expressions directly anyway. Instead
we'll use factory functions like:

std::auto_ptr<T>::new_(arg1, arg2, arg3)

which are equivalent to:

std::auto_ptr<T>(new T(arg1,arg2,arg3))

My 33 cents,
Dave
 
D

David Abrahams

David B. Held said:
I offered some examples. I suppose you think containers
with pointer semantics are "contrived", though, huh?

No. I do think that one shouldn't pass raw pointers to unmanaged
resources across their interface boundaries, though (or any interface
boundaries, for that matter). This is especially true because it
doesn't scale beyond one unmanaged resource parameter.
[...]
Ah, OK. But then if you want this guarantee to be useful,
*this can't be special at all;

I'm not sure where you got the idea that I said *this should
be handled specially.

It was your use of the word "external" in:

I contend that this level of safety is useful to identify,
because it is analogous to the strong guarantee
while not being the strong guarantee. You know
that foo's state is preserved, even if the external
state is not, and that can be a useful piece of
information in analyzing foo's behaviour in the
presence of exceptions.
Actually, I *was* explicit about which state is changeable.
The state that is changeable is dynamic resources whose
ownership is irreversibly transferred into the function.

Please, Dave: you didn't say that before. If you're saying that now,
fine. If you think I should've inferred it from what you said
earlier, please keep in mind that when you're talking about the EH
guarantees you're in the context of program specification and both
precision and explicitness count.

Try to think about how you'd write a precise description of your
guarantee, keeping in mind that transfer-of-ownership is a slippery
idea to nail down in specification. IMO the best way to deal with
situations like this one is to enforce the transfer-of-ownership in
the interface. Use auto_ptr if you can, or if you absolutely *must*
pass raw pointers, use a kind of smart pointer with implicit
conversion from T*. IMO the second-best way is to add to the
documentation "if an exception is thrown, p will be deleted". The
second-best way can be difficult when dealing with anything other than
constructors, though: _when_ will p be deleted? That's why it's only
second-best.
That turns out to be the only state which you would *want* to
change in order to have a good safety guarantee.

Sure, for some broad definition of "resource" (another slippery
notion).
Actually, there is. It promises to give you the strong
guarantee except that it will do one smart thing and
automatically clean up resources. In the sense that "smart"
is used in other contexts to imply automatic management,
especially of resources, I think it is quite reasonable.

It's not a complete idea without the notion of which unmanaged
resources are being transferred.

But that's a feature of the caller, not of the callee!
[...]
SearchableStack s;
T* p = new T;
s.push(p);
p->whatever();

If you want to make it into a feature of the callee, pass a
std::auto_ptr by value.

But if s were an auto_ptr<>, what would you do?

T* p = new T;
auto_ptr<T> s(p);

Why allow the inline new call in one context, but not another?

To limit the scope of danger.
Yes. But this is a case of externally giving a stronger
guarantee, which is just like externally giving a function
the strong guarantee by copying the modifiable state first.

No, it's the opposite. The guarantee is ensured *internally* to the
function, by its interface.
So then your argument is that no component should give
the strong guarantee because a client can always provide
it herself?

Huh?? How did you arrive at that conclusion?
If a function can provide the strong guarantee
efficiently, isn't it better if it does so?

Generally, but I don't see how that relates to the issue at hand.
[...]
But I think it does address what seems to be
a flaw, or at least, a peculiarity, of the strong
guarantee, without inventing new exception
guidelines arbitrarily.

It's neither a flaw nor a peculiarity of the strong
guarantee, IMO. It covers the case perfectly when you
use std::auto_ptr, and isn't designed to cover the case
otherwise.

But then, we don't need to have the strong guarantee,
since we can almost always provide it ourselves.

Your line of argument, if you'll excuse me for saying so, is pure
nuttiness. If std::vector::push_back didn't provide the strong
guarantee, how would you propose we can "provide it ourselves"?
Don't forget reset(). ;>

Personally I think having reset() at all is a design mistake. Show me
a reset() which can throw and I'll show you an even worse design
mistake.
Ok, you caught me. ;) I was hoping that containers with
pointer semantics would provide another justification,
but they probably wouldn't add considerably to the
number of users who want the smart guarantee.

Especially not if properly designed. Consider how the range inserter
that takes three iterators should work on a deque with pointer
semantics. There's no way to track resource management if you pass a
range of raw pointers.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top