|
Boost : |
From: Pavol Droba (droba_at_[hidden])
Date: 2003-10-24 07:19:52
On Fri, Oct 24, 2003 at 03:49:40AM -0500, David B. Held wrote:
> "Pavol Droba" <droba_at_[hidden]> wrote in message
> news:20031023211216.GC22941_at_lenin.felcer.sk...
> > [...]
> > An algorithm has no internal state, so its integrity as a
> > component is always preserved.
>
> You have to remember the context in which Dave wrote those
> definitions. He was looking at the standard library in particular,
> so he was looking at a lot of member functions. However, the
> only thing special about a member function is the hidden 'this'
> parameter. I think a better way to say it is that the basic
> guarantee ensures that a function preserves the invariants of
> its arguments and any global state it modifies. Phrased this
> way, it becomes more obvious that non-member functions
> don't automatically give the basic guarantee.
>
Yes, this is exactly how I understand it. Only you have described
it a much better way.
> > Algorithms can only provide exception guaranitie for
> > arguments they work with.
>
> Exactly.
>
> > In other words, this guarantie is always relative.
>
> False. The guarantee is defined in terms of the invariants
> of every argument that could be passed to the function. If
> the function's interface allows an argument with an invariant
> that the function does not preserve, then the function does
> not give the basic guarantee. You could argue that
> arguments could have contrived invariants that would make
> the basic guarantee impossible to ensure, but then, you
> could argue that the function's interface is documented to
> not allow those as valid arguments.
>
> Also note that a template function is really a family of
> actual functions, and each of those functions could provide
> a different guarantee. That is, the guarantee offered by a
> template function set could (and probably is) a function of
> the types of the template arguments (I've used the overloaded
> forms of 'function' and 'argument' way too many times in the
> last two paragraphs).
>
I was refering to the template functions (because all algorithms
in the string_algo lib are templated). My wording was not clear enough.
I agree with you and this is what I mean to say. If an algorithm
is a template, its guarantie is relative in respect to template
arguments.
> > [...]
> > 1. The library alway manages the external components only
> > through operations defined for them (i.e. it can never access
> > their internal state, if no operation allows it).
> > If all such operations provide "basic exception guarantie",
> > naturaly this guarantie is preserved by an algorithm
>
> That doesn't follow. The arguments to a function might allow
> operations that all give the basic guarantee, but that does not
> mean that the function itself gives the basic guarantee.
>
It there is nothing, that could corrupt an internal stare of
an argument, what can then break the rule?
> > 2. None of the algorithms in this library throws an exception
> > on its own, therefore if all operations used by a particular
> > algorithm provide "no-throw" guarantie, this guarantie is
> > preserved.
>
> This is a useful and important thing to note.
>
> > 3. "Strong" guarantie is always preserved for constant and
> > pass-by-value arguments. (this is obvious, but important to
> > mention)
>
> But arguments don't give exception safety guarantees. Only
> functions do, and if a function contains persistent state or
> has access to global or otherwise external data, then just
> because the arguments are const or pass-by-value does not
> mean that the function gives the strong guarantee. This may
> sound pedantic, but the whole point of exception safety is
> to make sure you don't get caught by one overlooked item.
> That's why it's important to explicitly state whether function
> f() gives the strong guarantee or not. Don't make the user
> look at the signature and go: "Oh, that only takes arguments
> by value, so I assume this functions gives the strong guarantee."
> And definitely don't make the user look at the implementation
> to determine the guarantee.
>
Ok.
> > These definitions are sound, but does not provide many hints,
> > what level of guarantie is given for a particular algorithm.
>
> Which is why you should state the guarantees as a function
> of the operations involved.
>
Sure, this is the only possibility.
> > [...]
> > So this is the summary. It would be possible to specify for
> > each algorithm a table with specific requirements to give the
> > specific guarantie. It would be quite chalenging to do it
> > properly,
>
> Indeed!
>
> > but the question is if it would be usable at all?
>
> Yes! I think this type of rigor is increasingly important.
> It's bad enough that most exceptions are more or less
> ignored in a lot of application code. It's about time we
> started paying more attention to exception safety.
> You can only evaluate the safety of your own code when
> you know the safety of the functions that your code calls.
> Thus, an exception safety analysis is something that I
> really think should be required of every Boost library.
Are you aware that such an analysis mean an enumeration
of all operations used in the algorithm?
Please see the example below. This is only a very simple
algorithm, counting +-20 lines of code. For more complicated algorithms,
such a specification can be VERY long and I dare to say, that
for an average user, quite unusable. For a reviewer, looking into
the code give just about the same information.
> > If requested, I can provide an example of such a table for
> > one algorithm.
>
> Please do.
>
An example: generic trim algorithm.
<example>
a) variant 1:
template<typename OutputIteratorT, typename InputContainerT, typename PredicateT>
OutputIteratorT trim_copy(
OutputIteratorT Output,
const InputContainerT & Input,
PredicateT IsSpace);
-Basic exception guarantie of this algorithm is give iff following operations
give this guarantie:
InputContainerT::const_iterator::operator++
::operator*
::operator-- // is const_iterator is BidirectionalIterator
::<copy-constructor>
::operator=
::operator==
::operator!=
PredicateT::operator( InputContainer::const_iterator::value_type )
OutputIteratorT::operator++
::operator*
::operator==
::operator!=
-"No-throw" guarantie is give, if all mentioned operations provide "no-throw" guarantie.
-"Strong" guarantie is given, if all mentioned operations provide "basic" guarantie, plus
OutputIteratorT provide "no-throw guarantie"
b) variant 2:
template< typename ContainerT, typename PredicateT >
inline InputContainerT trim_left_copy( const ContainerT& Input, PredicateT IsSpace );
-Basic exception guarantie of this algorithm is give iff following operations
give this guarantie:
ContainerT::const_iterator::operator++
::operator*
::operator-- // is const_iterator is BidirectionalIterator
::<copy-constructor>
::operator=
::operator==
::operator!=
ContainerT::ContainerT( ContainerT::const_iterator, ContainerT::const_iterator )
ContainerT::<copy-constructor>
PredicateT::operator( Container::const_iterator::value_type )
-"No-throw" guarantie is give, if all mentioned operations provide "no-throw" guarantie.
c) variant 3:
template<typename ContainerT, typename PredicateT>
ContainerT & trim(ContainerT & Input, PredicateT IsSpace);
-Basic exception guarantie of this algorithm is give iff following operations
give this guarantie:
ContainerT::const_iterator::operator++
::operator*
::operator-- // is const_iterator is BidirectionalIterator
::<copy-constructor>
::operator==
::operator!=
ContainerT::erase()
PredicateT::operator( Container::const_iterator::value_type )
-"No-throw" guarantie is give, if all mentioned operations provide "no-throw" guarantie.
</example>
To be honest, I don't find this specification very usable, but I don't know about an
other way to meat all goals you require.
Pavol.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk